{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 75 写文件及相对路径问题"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "建议\n",
    "\n",
    "1. **明确工作目录**：提供文档说明“需从项目根运行”或程序启动时打印当前工作目录，避免困惑。\n",
    "2. **使用绝对路径**：\n",
    "   - 配置文件、数据目录通过 CLI 参数、环境变量或配置文件提供绝对路径。\n",
    "   - 或者如示例 2，根据程序/源文件位置推导。\n",
    "3. **交叉平台处理**：使用 `path/filepath` 而非手写 `/`，确保 Windows、Unix 都能正常工作。\n",
    "4. **写文件权限**：Unix 常用 `0o644`（文件）与 `0o755`（目录），Windows 忽略该参数但仍需提"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 写文件的常用方式\n",
    "\n",
    "| 场景             | 常用 API                                    | 说明                                       |\n",
    "| ---------------- | ------------------------------------------- | ------------------------------------------ |\n",
    "| 一次性写入小文件 | `os.WriteFile`                              | 简洁、自动创建文件（会覆盖），可设置权限。 |\n",
    "| 连续写入或大文件 | `os.Create` + `bufio.Writer` / `File.Write` | 适用于分批写入、流式数据。                 |\n",
    "| 追加内容         | `os.OpenFile` 搭配 `O_APPEND`               | 打开已有文件并在末尾追加。                 |\n",
    "\n",
    "关键点：\n",
    "\n",
    "- 处理错误（`if err != nil`）：确保写入成功。\n",
    "- 合理的文件权限（`0644` 等）。\n",
    "- 需要写目录时先 `os.MkdirAll`。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 相对路径的常见问题\n",
    "\n",
    "- **相对路径永远基于“当前工作目录”** (`os.Getwd()`)，而不是源文件所在目录。\n",
    "- `go run`、`go test`、或在不同目录运行可执行文件时，工作目录可能不同。\n",
    "- 解决方案：\n",
    "  - 显式使用绝对路径（配置或环境变量提供）。\n",
    "  - 运行时通过 `os.Executable()` 或 `runtime.Caller` 推导程序目录，再拼接相对资源路径。\n",
    "  - 在测试/示例中说明需从项目根目录运行。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 示例"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 基础写文件与相对路径"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- 输出的 `Current working directory` 告诉你相对路径的基准。\n",
    "- 目录 `output` 会在当前工作目录下创建，因此如果你在别处运行二进制，文件位置也会随之变化"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Current working directory: /mnt/d/dev_learn/golang_from_beginner_to_mastery/03_file_processing\n",
      "Relative path written    : output/result.txt\n",
      "Absolute path            : /mnt/d/dev_learn/golang_from_beginner_to_mastery/03_file_processing/output/result.txt\n",
      "Done.\n"
     ]
    }
   ],
   "source": [
    "package main\n",
    "\n",
    "import (\n",
    "\t\"fmt\"\n",
    "\t\"os\"\n",
    "\t\"path/filepath\"\n",
    ")\n",
    "\n",
    "func main() {\n",
    "\t// 目标相对路径：output/result.txt\n",
    "\trelDir := \"output\"\n",
    "\tfileName := \"result.txt\"\n",
    "\tcontent := []byte(\"Hello, file writing in Go!\\n\")\n",
    "\n",
    "\t// 确保目录存在\n",
    "\tif err := os.MkdirAll(relDir, 0o755); err != nil {\n",
    "\t\tpanic(fmt.Errorf(\"failed to create directory %s: %w\", relDir, err))\n",
    "\t}\n",
    "\n",
    "\trelPath := filepath.Join(relDir, fileName)\n",
    "\n",
    "\t// 使用 os.WriteFile 一次性写入\n",
    "\tif err := os.WriteFile(relPath, content, 0o644); err != nil {\n",
    "\t\tpanic(fmt.Errorf(\"failed to write file %s: %w\", relPath, err))\n",
    "\t}\n",
    "\n",
    "\t// 显示当前工作目录与最终绝对路径\n",
    "\tcwd, err := os.Getwd()\n",
    "\tif err != nil {\n",
    "\t\tpanic(err)\n",
    "\t}\n",
    "\tabsPath, err := filepath.Abs(relPath)\n",
    "\tif err != nil {\n",
    "\t\tpanic(err)\n",
    "\t}\n",
    "\n",
    "\tfmt.Println(\"Current working directory:\", cwd)\n",
    "\tfmt.Println(\"Relative path written    :\", relPath)\n",
    "\tfmt.Println(\"Absolute path            :\", absPath)\n",
    "\tfmt.Println(\"Done.\")\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 解决相对路径依赖工作目录的问题"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- `runtime.Caller(0)` 获取当前源文件路径，避免依赖工作目录。\n",
    "- 也可换成 `os.Executable()` 获取已编译二进制所在位置，看需要放资源在源目录还是可执行目录"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "File written to: /tmp/gonb_ac3573ea/data/note.txt\n",
      "Current working directory: /mnt/d/dev_learn/golang_from_beginner_to_mastery/03_file_processing\n",
      "Executable path           : /tmp/gonb_ac3573ea/gonb_ac3573ea\n"
     ]
    }
   ],
   "source": [
    "package main\n",
    "\n",
    "import (\n",
    "\t\"fmt\"\n",
    "\t\"os\"\n",
    "\t\"path/filepath\"\n",
    "\t\"runtime\"\n",
    ")\n",
    "\n",
    "// dataDirRelative 返回与本源文件同级的 data 目录路径，无论程序从哪里运行。\n",
    "func dataDirRelative() (string, error) {\n",
    "\t_, filename, _, ok := runtime.Caller(0)\n",
    "\tif !ok {\n",
    "\t\treturn \"\", fmt.Errorf(\"runtime.Caller failed\")\n",
    "\t}\n",
    "\tsourceDir := filepath.Dir(filename)\n",
    "\treturn filepath.Join(sourceDir, \"data\"), nil\n",
    "}\n",
    "\n",
    "func main() {\n",
    "\tdataDir, err := dataDirRelative()\n",
    "\tif err != nil {\n",
    "\t\tpanic(err)\n",
    "\t}\n",
    "\n",
    "\tif err := os.MkdirAll(dataDir, 0o755); err != nil {\n",
    "\t\tpanic(fmt.Errorf(\"create data dir: %w\", err))\n",
    "\t}\n",
    "\n",
    "\ttargetPath := filepath.Join(dataDir, \"note.txt\")\n",
    "\tcontent := []byte(\"This file sits next to the source directory.\\n\")\n",
    "\n",
    "\tfile, err := os.Create(targetPath)\n",
    "\tif err != nil {\n",
    "\t\tpanic(fmt.Errorf(\"create file: %w\", err))\n",
    "\t}\n",
    "\tdefer file.Close()\n",
    "\n",
    "\tif _, err := file.Write(content); err != nil {\n",
    "\t\tpanic(fmt.Errorf(\"write file: %w\", err))\n",
    "\t}\n",
    "\n",
    "\tfmt.Println(\"File written to:\", targetPath)\n",
    "\n",
    "\t// 展示当前工作目录与可执行文件路径\n",
    "\tcwd, _ := os.Getwd()\n",
    "\texecPath, _ := os.Executable()\n",
    "\tfmt.Println(\"Current working directory:\", cwd)\n",
    "\tfmt.Println(\"Executable path           :\", execPath)\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 76 读文件"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "| 场景                       | 常用 API / 组合                    | 特点与适用场景                                               |\n",
    "| -------------------------- | ---------------------------------- | ------------------------------------------------------------ |\n",
    "| 一次性读完小文件           | `os.ReadFile`                      | 最简洁；内部自动 `Open`/`Read`/`Close`；适合配置、模板、小文本 |\n",
    "| 想要更多控制（如延迟关闭） | `os.Open` + `io.ReadAll`           | 需要手动 `Close`；可搭配 `io.LimitReader`、`io.Copy` 做更多处理 |\n",
    "| 行级读取、日志处理         | `bufio.Scanner`                    | 默认按行分割；需处理超长行（自定义 `Buffer`）                |\n",
    "| 自定义缓冲 / 二进制数据    | `bufio.Reader`                     | 提供 `ReadString`、`ReadBytes`、`Peek` 等；更灵活            |\n",
    "| 大文件流式处理             | `io.Copy`、循环读取固定缓冲        | 边读边处理，不占用大量内存                                   |\n",
    "| 需要专业解析               | `encoding/csv`、`encoding/json` 等 | 在读取基础上提供结构化解析                                   |\n",
    "\n",
    "> **注意**：相对路径仍然取决于程序运行时的当前工作目录 (`os.Getwd()`)，与源文件位置不同。必要时可通过 `runtime.Caller` 或 `os.Executable` 推导文件位置。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "提示\n",
    "\n",
    "1. **错误处理**：无论读写方式如何，都要检查返回的 `error`；特别是 `Scanner.Err()`。\n",
    "2. **EOF 处理**：`io.EOF` 是正常结束的信号，要区别于真正的错误。\n",
    "3. **缓冲区大小**：`bufio.Scanner` 默认行长 64 KiB，超长行需调用 `Scanner.Buffer` 设置更大的 max token。\n",
    "4. **文件编码**：默认按字节读取，如需特定编码（如 GBK），可结合 `golang.org/x/text/encoding`。\n",
    "5. **性能与内存**：\n",
    "   - 小文件：`os.ReadFile` 简单直接。\n",
    "   - 大文件：使用缓冲或流式处理，避免一次性读入内存。\n",
    "6. **相对路径**：请根据运行方式确认当前工作目录，必要时使用绝对路径或 `runtime.Caller` 等技巧获取资源路径。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## `os.ReadFile` 与 `io.ReadAll` 读取完整文件"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "== os.ReadFile ==\n",
      "《临江仙·滚滚长江东逝水》\n",
      "明·杨慎\n",
      "滚滚长江东逝水，浪花淘尽英雄。\n",
      "\n",
      "\n",
      "== os.Open + io.ReadAll ==\n",
      "《临江仙·滚滚长江东逝水》\n",
      "明·杨慎\n",
      "滚滚长江东逝水，浪花淘尽英雄。\n",
      "\n"
     ]
    }
   ],
   "source": [
    "package main\n",
    "\n",
    "import (\n",
    "\t\"fmt\"\n",
    "\t\"io\"\n",
    "\t\"os\"\n",
    "\t\"path/filepath\"\n",
    ")\n",
    "\n",
    "func main() {\n",
    "\t// 创建示例文件\n",
    "\tpath := filepath.Join(\"data\", \"poem.txt\")\n",
    "\tif err := seedFile(path); err != nil {\n",
    "\t\tpanic(err)\n",
    "\t}\n",
    "\n",
    "\tfmt.Println(\"== os.ReadFile ==\")\n",
    "\tcontent, err := os.ReadFile(path)\n",
    "\tif err != nil {\n",
    "\t\tpanic(fmt.Errorf(\"os.ReadFile: %w\", err))\n",
    "\t}\n",
    "\tfmt.Println(string(content))\n",
    "\n",
    "\tfmt.Println(\"\\n== os.Open + io.ReadAll ==\")\n",
    "\tf, err := os.Open(path)\n",
    "\tif err != nil {\n",
    "\t\tpanic(fmt.Errorf(\"os.Open: %w\", err))\n",
    "\t}\n",
    "\tdefer func() {\n",
    "\t\tif cerr := f.Close(); cerr != nil {\n",
    "\t\t\tfmt.Println(\"close error:\", cerr)\n",
    "\t\t}\n",
    "\t}()\n",
    "\n",
    "\tcontent2, err := io.ReadAll(f)\n",
    "\tif err != nil {\n",
    "\t\tpanic(fmt.Errorf(\"io.ReadAll: %w\", err))\n",
    "\t}\n",
    "\tfmt.Println(string(content2))\n",
    "}\n",
    "\n",
    "// seedFile 确保示例文件存在\n",
    "func seedFile(path string) error {\n",
    "\tif err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {\n",
    "\t\treturn fmt.Errorf(\"mkdir: %w\", err)\n",
    "\t}\n",
    "\tdata := \"《临江仙·滚滚长江东逝水》\\n明·杨慎\\n滚滚长江东逝水，浪花淘尽英雄。\\n\"\n",
    "\treturn os.WriteFile(path, []byte(data), 0o644)\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## `bufio.Scanner` 与 `bufio.Reader` 流式读取"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "== bufio.Scanner 行读取 ==\n",
      "01: line 01: sample log entry...\n",
      "02: line 02: sample log entry...\n",
      "03: line 03: sample log entry...\n",
      "总共读取 10 行\n",
      "\n",
      "== bufio.Reader 分块读取 ==\n",
      "读取 32 字节: \"line 01: sample log entry...\\nlin\"\n",
      "读取 32 字节: \"e 02: sample log entry...\\nline 0\"\n",
      "读取 32 字节: \"3: sample log entry...\\nline 04: \"\n",
      "读取 32 字节: \"sample log entry...\\nline 05: sam\"\n",
      "读取 32 字节: \"ple log entry...\\nline 06: sample\"\n",
      "读取 32 字节: \" log entry...\\nline 07: sample lo\"\n",
      "读取 32 字节: \"g entry...\\nline 08: sample log e\"\n",
      "读取 32 字节: \"ntry...\\nline 09: sample log entr\"\n",
      "读取 32 字节: \"y...\\nline 10: sample log entry..\"\n",
      "读取  2 字节: \".\\n\"\n",
      "读取结束\n",
      "总计读取 290 字节\n"
     ]
    }
   ],
   "source": [
    "package main\n",
    "\n",
    "import (\n",
    "\t\"bufio\"\n",
    "\t\"fmt\"\n",
    "\t\"io\"\n",
    "\t\"os\"\n",
    "\t\"path/filepath\"\n",
    ")\n",
    "\n",
    "func main() {\n",
    "\tpath := filepath.Join(\"data\", \"log.txt\")\n",
    "\tif err := seedLargeFile(path); err != nil {\n",
    "\t\tpanic(err)\n",
    "\t}\n",
    "\n",
    "\tfmt.Println(\"== bufio.Scanner 行读取 ==\")\n",
    "\tif err := readWithScanner(path); err != nil {\n",
    "\t\tpanic(err)\n",
    "\t}\n",
    "\n",
    "\tfmt.Println(\"\\n== bufio.Reader 分块读取 ==\")\n",
    "\tif err := readWithBufioReader(path); err != nil {\n",
    "\t\tpanic(err)\n",
    "\t}\n",
    "}\n",
    "\n",
    "func readWithScanner(path string) error {\n",
    "\tfile, err := os.Open(path)\n",
    "\tif err != nil {\n",
    "\t\treturn fmt.Errorf(\"open: %w\", err)\n",
    "\t}\n",
    "\tdefer file.Close()\n",
    "\n",
    "\tscanner := bufio.NewScanner(file)\n",
    "\t// 扩展缓冲区，防止超长行报错\n",
    "\tconst maxCapacity = 1024 * 1024 // 1 MiB\n",
    "\tbuf := make([]byte, 0, 64*1024)\n",
    "\tscanner.Buffer(buf, maxCapacity)\n",
    "\n",
    "\tlineCount := 0\n",
    "\tfor scanner.Scan() {\n",
    "\t\tlineCount++\n",
    "\t\tif lineCount <= 3 {\n",
    "\t\t\tfmt.Printf(\"%02d: %s\\n\", lineCount, scanner.Text())\n",
    "\t\t}\n",
    "\t}\n",
    "\tif err := scanner.Err(); err != nil {\n",
    "\t\treturn fmt.Errorf(\"scanner: %w\", err)\n",
    "\t}\n",
    "\tfmt.Printf(\"总共读取 %d 行\\n\", lineCount)\n",
    "\treturn nil\n",
    "}\n",
    "\n",
    "func readWithBufioReader(path string) error {\n",
    "\tfile, err := os.Open(path)\n",
    "\tif err != nil {\n",
    "\t\treturn fmt.Errorf(\"open: %w\", err)\n",
    "\t}\n",
    "\tdefer file.Close()\n",
    "\n",
    "\treader := bufio.NewReader(file)\n",
    "\tchunk := make([]byte, 32)\n",
    "\ttotal := 0\n",
    "\tfor {\n",
    "\t\tn, err := reader.Read(chunk)\n",
    "\t\tif n > 0 {\n",
    "\t\t\ttotal += n\n",
    "\t\t\tfmt.Printf(\"读取 %2d 字节: %q\\n\", n, chunk[:n])\n",
    "\t\t}\n",
    "\t\tif err != nil {\n",
    "\t\t\tif err == io.EOF {\n",
    "\t\t\t\tfmt.Println(\"读取结束\")\n",
    "\t\t\t\tbreak\n",
    "\t\t\t}\n",
    "\t\t\treturn fmt.Errorf(\"read: %w\", err)\n",
    "\t\t}\n",
    "\t}\n",
    "\tfmt.Printf(\"总计读取 %d 字节\\n\", total)\n",
    "\treturn nil\n",
    "}\n",
    "\n",
    "func seedLargeFile(path string) error {\n",
    "\tif err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {\n",
    "\t\treturn fmt.Errorf(\"mkdir: %w\", err)\n",
    "\t}\n",
    "\tfile, err := os.Create(path)\n",
    "\tif err != nil {\n",
    "\t\treturn fmt.Errorf(\"create: %w\", err)\n",
    "\t}\n",
    "\tdefer file.Close()\n",
    "\n",
    "\twriter := bufio.NewWriter(file)\n",
    "\tfor i := 1; i <= 10; i++ {\n",
    "\t\tif _, err := fmt.Fprintf(writer, \"line %02d: sample log entry...\\n\", i); err != nil {\n",
    "\t\t\treturn fmt.Errorf(\"write: %w\", err)\n",
    "\t\t}\n",
    "\t}\n",
    "\tif err := writer.Flush(); err != nil {\n",
    "\t\treturn fmt.Errorf(\"flush: %w\", err)\n",
    "\t}\n",
    "\treturn nil\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 77 用bufio读写文件"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "为什么使用 `bufio`\n",
    "\n",
    "- `bufio` 提供 **缓冲读写**，减少系统调用次数，显著提高频繁读写小块数据时的性能。\n",
    "- `bufio.Writer` 会先把数据写入内存缓冲区，缓冲满或调用 `Flush()` 时才写入底层 `io.Writer`（如文件）。\n",
    "- `bufio.Reader` 为底层 `io.Reader` 提供缓冲，并提供 `ReadString`、`ReadBytes`、`Peek` 等实用方法。\n",
    "- `bufio.Scanner` 是专门用于逐 token（默认逐行）扫描的工具，适合处理文本日志等按行读取的场景。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "关键注意事项\n",
    "\n",
    "- **写入后记得 `Flush()`**：否则数据可能还在缓冲区未写入文件。\n",
    "- **检查错误**：所有写读调用及 `Flush()`、`Scanner.Err()` 都应检查错误。\n",
    "- **缓冲区大小**：默认缓冲区大小通常足够，但 `Scanner` 处理超长行时需要调用 `Scanner.Buffer` 调整。\n",
    "- **相对路径**：仍然基于运行时工作目录，必要时使用绝对路径或配置路径。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 使用 bufio.Writer 写文件"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "写入完成： output/poem.txt\n"
     ]
    }
   ],
   "source": [
    "package main\n",
    "\n",
    "import (\n",
    "\t\"bufio\"\n",
    "\t\"fmt\"\n",
    "\t\"os\"\n",
    "\t\"path/filepath\"\n",
    ")\n",
    "\n",
    "func main() {\n",
    "\tpath := filepath.Join(\"output\", \"poem.txt\")\n",
    "\tif err := writePoem(path); err != nil {\n",
    "\t\tpanic(err)\n",
    "\t}\n",
    "\tfmt.Println(\"写入完成：\", path)\n",
    "}\n",
    "\n",
    "func writePoem(path string) error {\n",
    "\tif err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {\n",
    "\t\treturn fmt.Errorf(\"create dir: %w\", err)\n",
    "\t}\n",
    "\n",
    "\tfile, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0o644)\n",
    "\tif err != nil {\n",
    "\t\treturn fmt.Errorf(\"open file: %w\", err)\n",
    "\t}\n",
    "\tdefer func() {\n",
    "\t\tif cerr := file.Close(); cerr != nil {\n",
    "\t\t\tfmt.Println(\"close error:\", cerr)\n",
    "\t\t}\n",
    "\t}()\n",
    "\n",
    "\twriter := bufio.NewWriterSize(file, 16*1024) // 自定义 16 KiB 缓冲区\n",
    "\tlines := []string{\n",
    "\t\t\"纳兰性德\\n\",\n",
    "\t\t\"明月多情应笑我\\n\",\n",
    "\t\t\"笑我如今对景\\n\",\n",
    "\t}\n",
    "\n",
    "\tfor _, line := range lines {\n",
    "\t\tif _, err := writer.WriteString(line); err != nil {\n",
    "\t\t\treturn fmt.Errorf(\"write string: %w\", err)\n",
    "\t\t}\n",
    "\t}\n",
    "\n",
    "\tif err := writer.Flush(); err != nil {\n",
    "\t\treturn fmt.Errorf(\"flush: %w\", err)\n",
    "\t}\n",
    "\treturn nil\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 使用 `bufio.Reader` 与 `bufio.Scanner` 读文件"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "=== 使用 bufio.Reader 读取固定大小块 ===\n",
      "读到 12 字节: \"line 1: this\"\n",
      "读到 12 字节: \" is a log en\"\n",
      "读到 12 字节: \"try...\\nline \"\n",
      "读到 12 字节: \"2: this is a\"\n",
      "读到 12 字节: \" log entry..\"\n",
      "读到 12 字节: \".\\nline 3: th\"\n",
      "读到 12 字节: \"is is a log \"\n",
      "读到 12 字节: \"entry...\\nlin\"\n",
      "读到 12 字节: \"e 4: this is\"\n",
      "读到 12 字节: \" a log entry\"\n",
      "读到 12 字节: \"...\\nline 5: \"\n",
      "读到 12 字节: \"this is a lo\"\n",
      "读到 11 字节: \"g entry...\\n\"\n",
      "读取结束\n",
      "\n",
      "=== 使用 bufio.Scanner 按行扫描 ===\n",
      "第 1 行: line 1: this is a log entry...\n",
      "第 2 行: line 2: this is a log entry...\n",
      "第 3 行: line 3: this is a log entry...\n",
      "第 4 行: line 4: this is a log entry...\n",
      "第 5 行: line 5: this is a log entry...\n"
     ]
    }
   ],
   "source": [
    "package main\n",
    "\n",
    "import (\n",
    "\t\"bufio\"\n",
    "\t\"fmt\"\n",
    "\t\"io\"\n",
    "\t\"os\"\n",
    "\t\"path/filepath\"\n",
    ")\n",
    "\n",
    "func main() {\n",
    "\tpath := filepath.Join(\"data\", \"log.txt\")\n",
    "\tif err := seedLog(path); err != nil {\n",
    "\t\tpanic(err)\n",
    "\t}\n",
    "\n",
    "\tfmt.Println(\"=== 使用 bufio.Reader 读取固定大小块 ===\")\n",
    "\tif err := readWithBufioReader(path); err != nil {\n",
    "\t\tpanic(err)\n",
    "\t}\n",
    "\n",
    "\tfmt.Println(\"\\n=== 使用 bufio.Scanner 按行扫描 ===\")\n",
    "\tif err := readWithScanner(path); err != nil {\n",
    "\t\tpanic(err)\n",
    "\t}\n",
    "}\n",
    "\n",
    "func seedLog(path string) error {\n",
    "\tif err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {\n",
    "\t\treturn fmt.Errorf(\"mkdir: %w\", err)\n",
    "\t}\n",
    "\tfile, err := os.Create(path)\n",
    "\tif err != nil {\n",
    "\t\treturn fmt.Errorf(\"create: %w\", err)\n",
    "\t}\n",
    "\tdefer file.Close()\n",
    "\n",
    "\twriter := bufio.NewWriter(file)\n",
    "\tfor i := 1; i <= 5; i++ {\n",
    "\t\tif _, err := fmt.Fprintf(writer, \"line %d: this is a log entry...\\n\", i); err != nil {\n",
    "\t\t\treturn fmt.Errorf(\"write log: %w\", err)\n",
    "\t\t}\n",
    "\t}\n",
    "\treturn writer.Flush()\n",
    "}\n",
    "\n",
    "func readWithBufioReader(path string) error {\n",
    "\tfile, err := os.Open(path)\n",
    "\tif err != nil {\n",
    "\t\treturn fmt.Errorf(\"open: %w\", err)\n",
    "\t}\n",
    "\tdefer file.Close()\n",
    "\n",
    "\treader := bufio.NewReader(file)\n",
    "\tbuf := make([]byte, 12)\n",
    "\n",
    "\tfor {\n",
    "\t\tn, err := reader.Read(buf)\n",
    "\t\tif n > 0 {\n",
    "\t\t\tfmt.Printf(\"读到 %2d 字节: %q\\n\", n, buf[:n])\n",
    "\t\t}\n",
    "\t\tif err != nil {\n",
    "\t\t\tif err == io.EOF {\n",
    "\t\t\t\tfmt.Println(\"读取结束\")\n",
    "\t\t\t\treturn nil\n",
    "\t\t\t}\n",
    "\t\t\treturn fmt.Errorf(\"read: %w\", err)\n",
    "\t\t}\n",
    "\t}\n",
    "}\n",
    "\n",
    "func readWithScanner(path string) error {\n",
    "\tfile, err := os.Open(path)\n",
    "\tif err != nil {\n",
    "\t\treturn fmt.Errorf(\"open: %w\", err)\n",
    "\t}\n",
    "\tdefer file.Close()\n",
    "\n",
    "\tscanner := bufio.NewScanner(file)\n",
    "\t// 若每行可能很长，可扩展缓冲区：\n",
    "\t// buf := make([]byte, 0, 64*1024)\n",
    "\t// scanner.Buffer(buf, 1024*1024)\n",
    "\n",
    "\tlineNumber := 0\n",
    "\tfor scanner.Scan() {\n",
    "\t\tlineNumber++\n",
    "\t\tfmt.Printf(\"第 %d 行: %s\\n\", lineNumber, scanner.Text())\n",
    "\t}\n",
    "\tif err := scanner.Err(); err != nil {\n",
    "\t\treturn fmt.Errorf(\"scanner error: %w\", err)\n",
    "\t}\n",
    "\treturn nil\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 78* 练习：自行实现带缓冲的FileWriter"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 79 文件管理和目录遍历"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "建议\n",
    "\n",
    "1. **路径**：使用 `path/filepath` 操作路径，避免平台差异。\n",
    "2. **错误处理**：遍历时出现权限等错误要及时处理或跳过。\n",
    "3. **资源释放**：打开文件后使用 `defer file.Close()`。\n",
    "4. **性能**：大量文件写操作可结合 `bufio`；遍历时根据需求决定是否读取文件内容。\n",
    "5. **权限/所有权**：在 Unix 下可用 `Chmod/Chown`，在 Windows 权限处理有限。\n",
    "6. **递归删除**：慎用 `os.RemoveAll`，避免误删；部署前测试环境验证。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 核心标准库\n",
    "\n",
    "| 包              | 主要功能                                           |\n",
    "| --------------- | -------------------------------------------------- |\n",
    "| `os`            | 文件创建、删除、重命名、权限、`stat` 等基础操作    |\n",
    "| `io` / `io/fs`  | 抽象接口（`Reader`、`Writer`、`FS`），便于通用处理 |\n",
    "| `path/filepath` | 平台无关路径操作、遍历（`WalkDir`）、`Glob`        |\n",
    "| `bufio`         | 缓冲读写                                           |\n",
    "| `time`          | 配合 `FileInfo.ModTime()` 获取时间信息             |"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 文件管理经典操作\n",
    "\n",
    "| 操作          | 常用函数                                     | 说明                                      |\n",
    "| ------------- | -------------------------------------------- | ----------------------------------------- |\n",
    "| 新建文件/目录 | `os.Create`, `os.Mkdir`, `os.MkdirAll`       | `Create` 会截断已有文件                   |\n",
    "| 写入内容      | `os.WriteFile`, `File.Write`, `bufio.Writer` | 记得检查错误                              |\n",
    "| 读取文件信息  | `os.Stat`, `File.Stat`                       | 获取 `FileInfo` 结构                      |\n",
    "| 改名/移动     | `os.Rename`                                  | 同一文件系统高效，否则需手动拷贝          |\n",
    "| 复制文件      | `io.Copy` + 新旧 `os.File`                   | Go 标准库未直接提供 `CopyFile`            |\n",
    "| 修改权限      | `os.Chmod`, `os.Chown`                       | Windows 权限控制有限                      |\n",
    "| 删除文件/目录 | `os.Remove`, `os.RemoveAll`                  | `Remove` 删除空目录，`RemoveAll` 递归删除 |"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 目录遍历方式\n",
    "\n",
    "| 方法               | 适用场景       | 特点                             |\n",
    "| ------------------ | -------------- | -------------------------------- |\n",
    "| `os.ReadDir`       | 列出目录第一层 | 返回 `[]os.DirEntry`，可按需深入 |\n",
    "| `filepath.WalkDir` | 递归遍历       | 支持跳过目录、处理错误           |\n",
    "| `filepath.Glob`    | 模式匹配       | Shell 风格通配符，返回匹配路径   |\n",
    "| `io/fs` 接口       | 虚拟文件系统   | 可配合 `embed`、`fstest` 等      |"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 示例"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 文件管理操作示例"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "文件创建并写入完成： demo_workspace/hello.txt\n",
      "文件信息：\n",
      "  名称: hello.txt\n",
      "  大小: 38 字节\n",
      "  目录?: false\n",
      "  权限: -rwxrwxrwx\n",
      "  修改时间: 2025-09-30T01:37:21+08:00\n",
      "文件已重命名： demo_workspace/hello_renamed.txt\n",
      "文件已复制到： demo_workspace/hello_copy.txt\n",
      "原文件已删除\n",
      "复制文件已删除\n"
     ]
    }
   ],
   "source": [
    "package main\n",
    "\n",
    "import (\n",
    "\t\"fmt\"\n",
    "\t\"io\"\n",
    "\t\"os\"\n",
    "\t\"path/filepath\"\n",
    "\t\"time\"\n",
    ")\n",
    "\n",
    "func main() {\n",
    "\tbaseDir := \"demo_workspace\"\n",
    "\tif err := os.MkdirAll(baseDir, 0o755); err != nil {\n",
    "\t\tpanic(fmt.Errorf(\"mk base dir: %w\", err))\n",
    "\t}\n",
    "\tdefer func() {\n",
    "\t\tif err := os.RemoveAll(baseDir); err != nil {\n",
    "\t\t\tfmt.Println(\"cleanup error:\", err)\n",
    "\t\t}\n",
    "\t}()\n",
    "\n",
    "\tdemoPath := filepath.Join(baseDir, \"hello.txt\")\n",
    "\tif err := createAndWrite(demoPath); err != nil {\n",
    "\t\tpanic(err)\n",
    "\t}\n",
    "\n",
    "\tif err := showFileInfo(demoPath); err != nil {\n",
    "\t\tpanic(err)\n",
    "\t}\n",
    "\n",
    "\trenamedPath := filepath.Join(baseDir, \"hello_renamed.txt\")\n",
    "\tif err := os.Rename(demoPath, renamedPath); err != nil {\n",
    "\t\tpanic(fmt.Errorf(\"rename: %w\", err))\n",
    "\t}\n",
    "\tfmt.Println(\"文件已重命名：\", renamedPath)\n",
    "\n",
    "\tcopyPath := filepath.Join(baseDir, \"hello_copy.txt\")\n",
    "\tif err := copyFile(renamedPath, copyPath); err != nil {\n",
    "\t\tpanic(err)\n",
    "\t}\n",
    "\tfmt.Println(\"文件已复制到：\", copyPath)\n",
    "\n",
    "\tif err := os.Remove(renamedPath); err != nil {\n",
    "\t\tpanic(fmt.Errorf(\"remove original: %w\", err))\n",
    "\t}\n",
    "\tfmt.Println(\"原文件已删除\")\n",
    "\n",
    "\tif err := os.Remove(copyPath); err != nil {\n",
    "\t\tpanic(fmt.Errorf(\"remove copy: %w\", err))\n",
    "\t}\n",
    "\tfmt.Println(\"复制文件已删除\")\n",
    "}\n",
    "\n",
    "func createAndWrite(path string) error {\n",
    "\tcontent := \"Go 文件管理示例\\n第二行内容\\n\"\n",
    "\tif err := os.WriteFile(path, []byte(content), 0o644); err != nil {\n",
    "\t\treturn fmt.Errorf(\"write file: %w\", err)\n",
    "\t}\n",
    "\tfmt.Println(\"文件创建并写入完成：\", path)\n",
    "\treturn nil\n",
    "}\n",
    "\n",
    "func showFileInfo(path string) error {\n",
    "\tinfo, err := os.Stat(path)\n",
    "\tif err != nil {\n",
    "\t\treturn fmt.Errorf(\"stat: %w\", err)\n",
    "\t}\n",
    "\tfmt.Println(\"文件信息：\")\n",
    "\tfmt.Println(\"  名称:\", info.Name())\n",
    "\tfmt.Println(\"  大小:\", info.Size(), \"字节\")\n",
    "\tfmt.Println(\"  目录?:\", info.IsDir())\n",
    "\tfmt.Println(\"  权限:\", info.Mode())\n",
    "\tfmt.Println(\"  修改时间:\", info.ModTime().Format(time.RFC3339))\n",
    "\treturn nil\n",
    "}\n",
    "\n",
    "func copyFile(src, dst string) error {\n",
    "\tin, err := os.Open(src)\n",
    "\tif err != nil {\n",
    "\t\treturn fmt.Errorf(\"open src: %w\", err)\n",
    "\t}\n",
    "\tdefer in.Close()\n",
    "\n",
    "\tout, err := os.OpenFile(dst, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0o644)\n",
    "\tif err != nil {\n",
    "\t\treturn fmt.Errorf(\"open dst: %w\", err)\n",
    "\t}\n",
    "\tdefer func() {\n",
    "\t\tif cerr := out.Close(); cerr != nil {\n",
    "\t\t\tfmt.Println(\"close dst error:\", cerr)\n",
    "\t\t}\n",
    "\t}()\n",
    "\n",
    "\tif _, err := io.Copy(out, in); err != nil {\n",
    "\t\treturn fmt.Errorf(\"copy: %w\", err)\n",
    "\t}\n",
    "\treturn nil\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 目录遍历与过滤"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "=== 使用 os.ReadDir 列出第一层 ===\n",
      "- 目录: assets\n",
      "- 目录: docs\n",
      "- 目录: logs\n",
      "\n",
      "=== 使用 filepath.WalkDir 递归遍历 ===\n",
      "[根目录]\n",
      "[目录] assets\n",
      "  [文件] assets/logo.svg\n",
      "[目录] docs\n",
      "  [文件] docs/guide.txt\n",
      "  [文件] docs/readme.md\n",
      "[目录] logs\n",
      "  [文件] logs/app.log\n",
      "[目录] logs/old\n",
      "  -> 跳过旧日志目录\n"
     ]
    }
   ],
   "source": [
    "package main\n",
    "\n",
    "import (\n",
    "\t\"fmt\"\n",
    "\t\"os\"\n",
    "\t\"path/filepath\"\n",
    ")\n",
    "\n",
    "func main() {\n",
    "\troot := \"demo_tree\"\n",
    "\tif err := seedTree(root); err != nil {\n",
    "\t\tpanic(err)\n",
    "\t}\n",
    "\tdefer func() {\n",
    "\t\tif err := os.RemoveAll(root); err != nil {\n",
    "\t\t\tfmt.Println(\"cleanup error:\", err)\n",
    "\t\t}\n",
    "\t}()\n",
    "\n",
    "\tfmt.Println(\"=== 使用 os.ReadDir 列出第一层 ===\")\n",
    "\tif err := listTopLevel(root); err != nil {\n",
    "\t\tpanic(err)\n",
    "\t}\n",
    "\n",
    "\tfmt.Println(\"\\n=== 使用 filepath.WalkDir 递归遍历 ===\")\n",
    "\tif err := walkTree(root); err != nil {\n",
    "\t\tpanic(err)\n",
    "\t}\n",
    "}\n",
    "\n",
    "func seedTree(root string) error {\n",
    "\tpaths := []string{\n",
    "\t\tfilepath.Join(root, \"docs\", \"readme.md\"),\n",
    "\t\tfilepath.Join(root, \"docs\", \"guide.txt\"),\n",
    "\t\tfilepath.Join(root, \"logs\", \"app.log\"),\n",
    "\t\tfilepath.Join(root, \"logs\", \"old\", \"2024-01.log\"),\n",
    "\t\tfilepath.Join(root, \"assets\", \"logo.svg\"),\n",
    "\t}\n",
    "\tfor _, p := range paths {\n",
    "\t\tif err := os.MkdirAll(filepath.Dir(p), 0o755); err != nil {\n",
    "\t\t\treturn fmt.Errorf(\"mkdir: %w\", err)\n",
    "\t\t}\n",
    "\t\tif err := os.WriteFile(p, []byte(\"demo content\\n\"), 0o644); err != nil {\n",
    "\t\t\treturn fmt.Errorf(\"write file %s: %w\", p, err)\n",
    "\t\t}\n",
    "\t}\n",
    "\treturn nil\n",
    "}\n",
    "\n",
    "func listTopLevel(root string) error {\n",
    "\tentries, err := os.ReadDir(root)\n",
    "\tif err != nil {\n",
    "\t\treturn fmt.Errorf(\"readdir: %w\", err)\n",
    "\t}\n",
    "\tfor _, entry := range entries {\n",
    "\t\tinfoType := \"文件\"\n",
    "\t\tif entry.IsDir() {\n",
    "\t\t\tinfoType = \"目录\"\n",
    "\t\t}\n",
    "\t\tfmt.Printf(\"- %s: %s\\n\", infoType, entry.Name())\n",
    "\t}\n",
    "\treturn nil\n",
    "}\n",
    "\n",
    "func walkTree(root string) error {\n",
    "\treturn filepath.WalkDir(root, func(path string, d os.DirEntry, err error) error {\n",
    "\t\tif err != nil {\n",
    "\t\t\tfmt.Println(\"访问错误:\", err)\n",
    "\t\t\treturn nil // 返回错误会终止遍历，可按需处理\n",
    "\t\t}\n",
    "\n",
    "\t\trel, _ := filepath.Rel(root, path)\n",
    "\t\tif rel == \".\" {\n",
    "\t\t\tfmt.Println(\"[根目录]\")\n",
    "\t\t\treturn nil\n",
    "\t\t}\n",
    "\n",
    "\t\tif d.IsDir() {\n",
    "\t\t\tfmt.Printf(\"[目录] %s\\n\", rel)\n",
    "\t\t\t// 示例：跳过 logs/old 子树\n",
    "\t\t\tif rel == filepath.Join(\"logs\", \"old\") {\n",
    "\t\t\t\tfmt.Println(\"  -> 跳过旧日志目录\")\n",
    "\t\t\t\treturn filepath.SkipDir\n",
    "\t\t\t}\n",
    "\t\t\treturn nil\n",
    "\t\t}\n",
    "\n",
    "\t\tfmt.Printf(\"  [文件] %s\\n\", rel)\n",
    "\t\treturn nil\n",
    "\t})\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 80* 文件练习：图像的分割与合并"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 81 高级IO功能"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "| 功能                      | 核心概念                                                     | 关键 API                           | 典型应用                             | 注意要点                                   |\n",
    "| ------------------------- | ------------------------------------------------------------ | ---------------------------------- | ------------------------------------ | ------------------------------------------ |\n",
    "| MultiWriter               | 将一个写入操作同时广播到多个 `io.Writer`                     | `io.MultiWriter`                   | 日志同时写入多个文件/标准输出        | 任一目标 `Writer` 出错会导致整体写入失败   |\n",
    "| LimitReader               | 对读取的最大字节数进行硬性限制                               | `io.LimitReader`                   | 限制一次请求或消息体大小             | 读取到上限后返回 `EOF`，不会修改原始数据源 |\n",
    "| 自定义 Reader（手动限流） | 通过实现 `io.Reader` 接口自定义读取逻辑（示例：只允许读前 N 字节） | 实现 `Read(p []byte) (int, error)` | 精细控制读取策略，如协议解析         | 需要正确维护内部状态和错误返回             |\n",
    "| MultiReader               | 将多个 Reader 串联为一个连续流                               | `io.MultiReader`                   | 按顺序合并多个数据源                 | 某个 Reader 返回非 `EOF` 的错误会立即中止  |\n",
    "| TeeReader                 | 在读取数据的同时将数据复制到另一个 Writer                    | `io.TeeReader`                     | 一边消费数据一边备份/校验            | 下游 Writer 阻塞会阻塞读取方               |\n",
    "| Pipe                      | 创建内存中无缓冲的“管道”连接 Reader/Writer                   | `io.Pipe`                          | 协程间流式传输数据，构建自定义流水线 | 读写必须并发进行；须处理同步和关闭逻辑     |\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 示例"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### `io.MultiWriter` 同步写日志到多个文件"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "日志已同时写入多个文件： logs\n"
     ]
    }
   ],
   "source": [
    "package main\n",
    "\n",
    "import (\n",
    "\t\"fmt\"\n",
    "\t\"io\"\n",
    "\t\"log\"\n",
    "\t\"os\"\n",
    "\t\"path/filepath\"\n",
    ")\n",
    "\n",
    "func main() {\n",
    "\tlogDir := \"logs\"\n",
    "\tif err := os.MkdirAll(logDir, 0o755); err != nil {\n",
    "\t\tlog.Fatalf(\"mkdir: %v\", err)\n",
    "\t}\n",
    "\n",
    "\tfiles, cleanup, err := openLogFiles(logDir, []string{\n",
    "\t\t\"app.log\", \"error.log\", \"audit.log\", \"mirror.log\",\n",
    "\t})\n",
    "\tif err != nil {\n",
    "\t\tlog.Fatalf(\"open files: %v\", err)\n",
    "\t}\n",
    "\tdefer cleanup()\n",
    "\n",
    "\tmulti := io.MultiWriter(files...)\n",
    "\n",
    "\tlogger := log.New(multi, \"[APP] \", log.LstdFlags|log.Lshortfile)\n",
    "\n",
    "\tfor i := 1; i <= 3; i++ {\n",
    "\t\tlogger.Printf(\"processing request %d ...\", i)\n",
    "\t}\n",
    "\n",
    "\tfmt.Println(\"日志已同时写入多个文件：\", logDir)\n",
    "}\n",
    "\n",
    "func openLogFiles(dir string, names []string) ([]io.Writer, func(), error) {\n",
    "\twriters := make([]io.Writer, 0, len(names))\n",
    "\tclosers := make([]*os.File, 0, len(names))\n",
    "\n",
    "\tfor _, name := range names {\n",
    "\t\tpath := filepath.Join(dir, name)\n",
    "\t\tf, err := os.OpenFile(path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o644)\n",
    "\t\tif err != nil {\n",
    "\t\t\tfor _, c := range closers {\n",
    "\t\t\t\tc.Close()\n",
    "\t\t\t}\n",
    "\t\t\treturn nil, nil, fmt.Errorf(\"open %s: %w\", path, err)\n",
    "\t\t}\n",
    "\t\tclosers = append(closers, f)\n",
    "\t\twriters = append(writers, f)\n",
    "\t}\n",
    "\n",
    "\tcleanup := func() {\n",
    "\t\tfor _, c := range closers {\n",
    "\t\t\tif err := c.Close(); err != nil {\n",
    "\t\t\t\tlog.Println(\"close error:\", err)\n",
    "\t\t\t}\n",
    "\t\t}\n",
    "\t}\n",
    "\treturn writers, cleanup, nil\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      ".\n",
      "├── 03_file_processing.ipynb\n",
      "└── logs\n",
      "    ├── app.log\n",
      "    ├── audit.log\n",
      "    ├── error.log\n",
      "    └── mirror.log\n",
      "\n",
      "1 directory, 5 files\n"
     ]
    }
   ],
   "source": [
    "! tree"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### `io.LimitReader` 限制读取字节数"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "第一次读取：n=6, err=<nil>, data=\"daqiao\"\n",
      "第二次读取：n=0, err=EOF\n"
     ]
    }
   ],
   "source": [
    "package main\n",
    "\n",
    "import (\n",
    "\t\"fmt\"\n",
    "\t\"io\"\n",
    "\t\"strings\"\n",
    ")\n",
    "\n",
    "func main() {\n",
    "\tsource := strings.NewReader(\"daqiaoqiao\")\n",
    "\tlimited := io.LimitReader(source, 6)\n",
    "\n",
    "\tbuf := make([]byte, 8)\n",
    "\n",
    "\tn, err := limited.Read(buf)\n",
    "\tfmt.Printf(\"第一次读取：n=%d, err=%v, data=%q\\n\", n, err, buf[:n])\n",
    "\n",
    "\tn, err = limited.Read(buf)\n",
    "\tfmt.Printf(\"第二次读取：n=%d, err=%v\\n\", n, err)\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 自定义 `Reader`（手动限制读取长度）"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "读到：\"daqi\"\n",
      "读到：\"ao\"\n",
      "结束原因： EOF\n"
     ]
    }
   ],
   "source": [
    "package main\n",
    "\n",
    "import (\n",
    "\t\"errors\"\n",
    "\t\"fmt\"\n",
    "\t\"io\"\n",
    "\t\"strings\"\n",
    ")\n",
    "\n",
    "type ManualLimiter struct {\n",
    "\tsrc   io.Reader\n",
    "\tleft  int64\n",
    "}\n",
    "\n",
    "func NewManualLimiter(r io.Reader, max int64) *ManualLimiter {\n",
    "\treturn &ManualLimiter{src: r, left: max}\n",
    "}\n",
    "\n",
    "func (ml *ManualLimiter) Read(p []byte) (int, error) {\n",
    "\tif ml.left <= 0 {\n",
    "\t\treturn 0, io.EOF\n",
    "\t}\n",
    "\tif int64(len(p)) > ml.left {\n",
    "\t\tp = p[:ml.left]\n",
    "\t}\n",
    "\tn, err := ml.src.Read(p)\n",
    "\tml.left -= int64(n)\n",
    "\tif err != nil {\n",
    "\t\tif errors.Is(err, io.EOF) && ml.left > 0 {\n",
    "\t\t\treturn n, io.EOF\n",
    "\t\t}\n",
    "\t\treturn n, err\n",
    "\t}\n",
    "\tif ml.left == 0 {\n",
    "\t\treturn n, io.EOF\n",
    "\t}\n",
    "\treturn n, nil\n",
    "}\n",
    "\n",
    "func main() {\n",
    "\tsrc := strings.NewReader(\"daqiaoqiao\")\n",
    "\treader := NewManualLimiter(src, 6)\n",
    "\n",
    "\tbuf := make([]byte, 4)\n",
    "\tfor {\n",
    "\t\tn, err := reader.Read(buf)\n",
    "\t\tif n > 0 {\n",
    "\t\t\tfmt.Printf(\"读到：%q\\n\", buf[:n])\n",
    "\t\t}\n",
    "\t\tif err != nil {\n",
    "\t\t\tfmt.Println(\"结束原因：\", err)\n",
    "\t\t\tbreak\n",
    "\t\t}\n",
    "\t}\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### `io.MultiReader` 串联多个数据源"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "《将进酒》\n",
      "君不见，黄河之水天上来，奔流到海不复回。\n",
      "君不见，高堂明镜悲白发，朝如青丝暮成雪。\n",
      "人生得意须尽欢，莫使金樽空对月。\n"
     ]
    }
   ],
   "source": [
    "package main\n",
    "\n",
    "import (\n",
    "\t\"fmt\"\n",
    "\t\"io\"\n",
    "\t\"os\"\n",
    "\t\"strings\"\n",
    ")\n",
    "\n",
    "func main() {\n",
    "\tr := io.MultiReader(\n",
    "\t\tstrings.NewReader(\"《将进酒》\\n\"),\n",
    "\t\tstrings.NewReader(\"君不见，黄河之水天上来，奔流到海不复回。\\n\"),\n",
    "\t\tstrings.NewReader(\"君不见，高堂明镜悲白发，朝如青丝暮成雪。\\n\"),\n",
    "\t\tstrings.NewReader(\"人生得意须尽欢，莫使金樽空对月。\\n\"),\n",
    "\t)\n",
    "\n",
    "\tif _, err := io.Copy(os.Stdout, r); err != nil {\n",
    "\t\tfmt.Println(\"copy error:\", err)\n",
    "\t}\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### `io.TeeReader` 一边消费一边复制"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "消费者读取到： 黄梅时节家家雨，青草池塘处处蛙。\n",
      "镜像写入到： 黄梅时节家家雨，青草池塘处处蛙。\n"
     ]
    }
   ],
   "source": [
    "package main\n",
    "\n",
    "import (\n",
    "\t\"bytes\"\n",
    "\t\"fmt\"\n",
    "\t\"io\"\n",
    "\t\"strings\"\n",
    ")\n",
    "\n",
    "func main() {\n",
    "\tsrc := strings.NewReader(\"黄梅时节家家雨，青草池塘处处蛙。\")\n",
    "\tvar mirror bytes.Buffer\n",
    "\n",
    "\ttee := io.TeeReader(src, &mirror)\n",
    "\n",
    "\tcontent, err := io.ReadAll(tee)\n",
    "\tif err != nil {\n",
    "\t\tpanic(err)\n",
    "\t}\n",
    "\n",
    "\tfmt.Println(\"消费者读取到：\", string(content))\n",
    "\tfmt.Println(\"镜像写入到：\", mirror.String())\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### `io.Pipe` 构建无缓冲数据通道"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "读者收到：\"春江潮水连海平，\\n\"\n",
      "读者收到：\"海上明月共潮生。\\n\"\n",
      "读者收到：\"滟滟随波千万里，\\n\"\n",
      "读者收到：\"何处春江无月明。\\n\"\n",
      "Pipe 示例结束\n"
     ]
    }
   ],
   "source": [
    "package main\n",
    "\n",
    "import (\n",
    "\t\"fmt\"\n",
    "\t\"io\"\n",
    "\t\"time\"\n",
    ")\n",
    "\n",
    "func main() {\n",
    "\tpr, pw := io.Pipe()\n",
    "\n",
    "\tdone := make(chan struct{})\n",
    "\n",
    "\tgo func() {\n",
    "\t\tdefer close(done)\n",
    "\t\tdefer pw.Close()\n",
    "\n",
    "\t\tmessage := []string{\n",
    "\t\t\t\"春江潮水连海平，\",\n",
    "\t\t\t\"海上明月共潮生。\",\n",
    "\t\t\t\"滟滟随波千万里，\",\n",
    "\t\t\t\"何处春江无月明。\",\n",
    "\t\t}\n",
    "\t\tfor _, line := range message {\n",
    "\t\t\tif _, err := pw.Write([]byte(line + \"\\n\")); err != nil {\n",
    "\t\t\t\tfmt.Println(\"writer error:\", err)\n",
    "\t\t\t\treturn\n",
    "\t\t\t}\n",
    "\t\t\ttime.Sleep(200 * time.Millisecond)\n",
    "\t\t}\n",
    "\t}()\n",
    "\n",
    "\tgo func() {\n",
    "\t\tdefer close(done)\n",
    "\t\tdefer pr.Close()\n",
    "\n",
    "\t\tbuf := make([]byte, 32)\n",
    "\t\tfor {\n",
    "\t\t\tn, err := pr.Read(buf)\n",
    "\t\t\tif n > 0 {\n",
    "\t\t\t\tfmt.Printf(\"读者收到：%q\\n\", buf[:n])\n",
    "\t\t\t}\n",
    "\t\t\tif err != nil {\n",
    "\t\t\t\tif err != io.EOF {\n",
    "\t\t\t\t\tfmt.Println(\"reader error:\", err)\n",
    "\t\t\t\t}\n",
    "\t\t\t\treturn\n",
    "\t\t\t}\n",
    "\t\t}\n",
    "\t}()\n",
    "\n",
    "\t<-done\n",
    "\t<-done\n",
    "\tfmt.Println(\"Pipe 示例结束\")\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 82 文件的压缩和解压，Reader和Writer接口"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "| 知识点    | 核心内容                              | 关键步骤                                       | 技术要点                               |\n",
    "| --------- | ------------------------------------- | ---------------------------------------------- | -------------------------------------- |\n",
    "| 文件拷贝  | 利用 `io.Copy` 简化读写               | 打开源/目标 → `io.Copy(dst, src)` → 关闭       | `os.File` 同时实现 Reader & Writer     |\n",
    "| GZIP 压缩 | `compress/gzip` 提供 `io.WriteCloser` | `gzip.NewWriter` → `io.Copy` → `Close`         | `Close` 刷新压缩缓冲区，统计大小对比   |\n",
    "| ZLIB 压缩 | `compress/zlib` 替代算法              | 选择算法 → 统一接口 → `io.Copy`                | 用 `switch` 组合多算法，接口隐藏差异   |\n",
    "| 文件解压  | 使用 `gzip.NewReader` 等              | `NewReader` → `io.Copy(dst, reader)` → `Close` | 输出文件权限/路径控制，错误处理严谨    |\n",
    "| 接口设计  | Go 的隐式接口机制                     | 结构体实现接口方法即可                         | `io.Copy` 参数是接口类型，支持任意实现 |"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 核心接口概念"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "| 接口                            | 方法签名                                   | 说明                                                    | 常见实现                                      |\n",
    "| ------------------------------- | ------------------------------------------ | ------------------------------------------------------- | --------------------------------------------- |\n",
    "| `io.Reader`                     | `Read(p []byte) (int, error)`              | 从数据源填充字节切片，返回读取字节数                    | `*os.File`、`*gzip.Reader`、`bytes.Buffer` 等 |\n",
    "| `io.Writer`                     | `Write(p []byte) (int, error)`             | 将字节切片写入目标                                      | `*os.File`、`*gzip.Writer`、`net.Conn` 等     |\n",
    "| `io.ReadCloser`                 | `Read` + `Close`                           | 读取并可关闭底层资源                                    | `gzip.NewReader` 返回值等                     |\n",
    "| `io.WriteCloser`                | `Write` + `Close`                          | 写入并可关闭底层资源                                    | `gzip.NewWriter` 返回值等                     |\n",
    "| `io.ReaderFrom` / `io.WriterTo` | `ReadFrom(r Reader)` / `WriteTo(w Writer)` | 支持直接从/向其他接口拷贝                               | `*os.File`、`bytes.Buffer` 等                 |\n",
    "| `io.Copy(dst, src)`             | 高阶函数                                   | 只要满足 Reader/Writer 接口即可直接拷贝数据（含压缩流） |                                               |\n",
    "\n",
    "> Go 接口采用 **隐式实现**：只要类型方法集匹配接口签名，即可被视为实现接口，无需显式声明。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 文件操作经典流程"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- **文件拷贝**：`os.Open` + `os.OpenFile` + `io.Copy(dst, src)`。\n",
    "- **压缩**：`gzip.NewWriter(dst)` → `io.Copy(gzipWriter, src)` → `Close()`。\n",
    "- **解压**：`gzip.NewReader(src)` → `io.Copy(dst, gzipReader)` → `Close()`。\n",
    "- **策略扩展**：利用 `compress/gzip`、`compress/zlib` 等不同压缩器，统一通过 `io.WriteCloser`/`io.Reader` 接口处理。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 优化要点"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- `io.Copy` 利用了接口多态，避免手写循环，提高可读性和性能。\n",
    "- 压缩器/解压器必须 `Close()`，以刷新缓冲区并释放资源。\n",
    "- 文件大小对比可通过 `os.Stat().Size()` 获取。\n",
    "- 合理的错误处理和资源释放是稳定性的保障。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 示例"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 使用 io.Copy 拷贝文件"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "共复制 59 字节\n",
      "拷贝完成： data/copy.txt\n"
     ]
    }
   ],
   "source": [
    "package main\n",
    "\n",
    "import (\n",
    "\t\"fmt\"\n",
    "\t\"io\"\n",
    "\t\"os\"\n",
    "\t\"path/filepath\"\n",
    ")\n",
    "\n",
    "func main() {\n",
    "\tsrc := filepath.Join(\"data\", \"source.txt\")\n",
    "\tdst := filepath.Join(\"data\", \"copy.txt\")\n",
    "\n",
    "\tif err := seedSource(src); err != nil {\n",
    "\t\tpanic(err)\n",
    "\t}\n",
    "\n",
    "\tif err := copyFile(src, dst); err != nil {\n",
    "\t\tpanic(err)\n",
    "\t}\n",
    "\n",
    "\tfmt.Println(\"拷贝完成：\", dst)\n",
    "}\n",
    "\n",
    "func seedSource(path string) error {\n",
    "\tif err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {\n",
    "\t\treturn fmt.Errorf(\"mkdir: %w\", err)\n",
    "\t}\n",
    "\tcontent := \"Go 文件拷贝示例\\n使用 io.Copy 简化读写逻辑。\\n\"\n",
    "\treturn os.WriteFile(path, []byte(content), 0o644)\n",
    "}\n",
    "\n",
    "func copyFile(src, dst string) error {\n",
    "\tin, err := os.Open(src)\n",
    "\tif err != nil {\n",
    "\t\treturn fmt.Errorf(\"open src: %w\", err)\n",
    "\t}\n",
    "\tdefer in.Close()\n",
    "\n",
    "\tout, err := os.OpenFile(dst, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0o644)\n",
    "\tif err != nil {\n",
    "\t\treturn fmt.Errorf(\"open dst: %w\", err)\n",
    "\t}\n",
    "\tdefer func() {\n",
    "\t\tif cerr := out.Close(); cerr != nil {\n",
    "\t\t\tfmt.Println(\"close dst error:\", cerr)\n",
    "\t\t}\n",
    "\t}()\n",
    "\n",
    "\twritten, err := io.Copy(out, in)\n",
    "\tif err != nil {\n",
    "\t\treturn fmt.Errorf(\"copy: %w\", err)\n",
    "\t}\n",
    "\tfmt.Printf(\"共复制 %d 字节\\n\", written)\n",
    "\treturn nil\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### GZIP 压缩文件"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "原始文件: data/sample.txt (107 字节)\n",
      "压缩文件: data/sample.txt.gz (132 字节)\n"
     ]
    }
   ],
   "source": [
    "package main\n",
    "\n",
    "import (\n",
    "\t\"compress/gzip\"\n",
    "\t\"fmt\"\n",
    "\t\"io\"\n",
    "\t\"os\"\n",
    "\t\"path/filepath\"\n",
    ")\n",
    "\n",
    "func main() {\n",
    "\tsrc := filepath.Join(\"data\", \"sample.txt\")\n",
    "\tgzPath := src + \".gz\"\n",
    "\n",
    "\tif err := seedSample(src); err != nil {\n",
    "\t\tpanic(err)\n",
    "\t}\n",
    "\n",
    "\tif err := compressGzip(src, gzPath); err != nil {\n",
    "\t\tpanic(err)\n",
    "\t}\n",
    "\n",
    "\tshowSize(src, gzPath)\n",
    "}\n",
    "\n",
    "func seedSample(path string) error {\n",
    "\tif err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {\n",
    "\t\treturn fmt.Errorf(\"mkdir: %w\", err)\n",
    "\t}\n",
    "\tcontent := []byte(\"黄河之水天上来，奔流到海不复回。\\n\" +\n",
    "\t\t\"君不见高堂明镜悲白发，朝如青丝暮成雪。\\n\")\n",
    "\treturn os.WriteFile(path, content, 0o644)\n",
    "}\n",
    "\n",
    "func compressGzip(src, dst string) error {\n",
    "\tin, err := os.Open(src)\n",
    "\tif err != nil {\n",
    "\t\treturn fmt.Errorf(\"open src: %w\", err)\n",
    "\t}\n",
    "\tdefer in.Close()\n",
    "\n",
    "\tout, err := os.OpenFile(dst, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0o644)\n",
    "\tif err != nil {\n",
    "\t\treturn fmt.Errorf(\"open dst: %w\", err)\n",
    "\t}\n",
    "\tdefer func() {\n",
    "\t\tif cerr := out.Close(); cerr != nil {\n",
    "\t\t\tfmt.Println(\"close dst error:\", cerr)\n",
    "\t\t}\n",
    "\t}()\n",
    "\n",
    "\tgzWriter := gzip.NewWriter(out)\n",
    "\tdefer func() {\n",
    "\t\tif cerr := gzWriter.Close(); cerr != nil {\n",
    "\t\t\tfmt.Println(\"gzip close error:\", cerr)\n",
    "\t\t}\n",
    "\t}()\n",
    "\n",
    "\tif _, err := io.Copy(gzWriter, in); err != nil {\n",
    "\t\treturn fmt.Errorf(\"copy to gzip: %w\", err)\n",
    "\t}\n",
    "\treturn nil\n",
    "}\n",
    "\n",
    "func showSize(original, compressed string) {\n",
    "\torigStat, err := os.Stat(original)\n",
    "\tif err != nil {\n",
    "\t\tfmt.Println(\"stat original:\", err)\n",
    "\t\treturn\n",
    "\t}\n",
    "\tcompStat, err := os.Stat(compressed)\n",
    "\tif err != nil {\n",
    "\t\tfmt.Println(\"stat compressed:\", err)\n",
    "\t\treturn\n",
    "\t}\n",
    "\tfmt.Printf(\"原始文件: %s (%d 字节)\\n\", original, origStat.Size())\n",
    "\tfmt.Printf(\"压缩文件: %s (%d 字节)\\n\", compressed, compStat.Size())\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### GZIP 解压文件"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "解压完成： data/sample_uncompressed.txt\n"
     ]
    }
   ],
   "source": [
    "package main\n",
    "\n",
    "import (\n",
    "\t\"compress/gzip\"\n",
    "\t\"fmt\"\n",
    "\t\"io\"\n",
    "\t\"os\"\n",
    "\t\"path/filepath\"\n",
    ")\n",
    "\n",
    "func main() {\n",
    "\tsrc := filepath.Join(\"data\", \"sample.txt\")\n",
    "\tgzPath := src + \".gz\"\n",
    "\textractPath := filepath.Join(\"data\", \"sample_uncompressed.txt\")\n",
    "\n",
    "\tif err := seedAndCompress(src, gzPath); err != nil {\n",
    "\t\tpanic(err)\n",
    "\t}\n",
    "\n",
    "\tif err := decompressGzip(gzPath, extractPath); err != nil {\n",
    "\t\tpanic(err)\n",
    "\t}\n",
    "\n",
    "\tfmt.Println(\"解压完成：\", extractPath)\n",
    "}\n",
    "\n",
    "func seedAndCompress(src, gzPath string) error {\n",
    "\tif err := os.MkdirAll(filepath.Dir(src), 0o755); err != nil {\n",
    "\t\treturn fmt.Errorf(\"mkdir: %w\", err)\n",
    "\t}\n",
    "\tpayload := []byte(\"青青子衿，悠悠我心。纵我不往，子宁不嗣音。\\n\")\n",
    "\tif err := os.WriteFile(src, payload, 0o644); err != nil {\n",
    "\t\treturn fmt.Errorf(\"write src: %w\", err)\n",
    "\t}\n",
    "\treturn compress(src, gzPath)\n",
    "}\n",
    "\n",
    "func compress(src, dst string) error {\n",
    "\tin, err := os.Open(src)\n",
    "\tif err != nil {\n",
    "\t\treturn err\n",
    "\t}\n",
    "\tdefer in.Close()\n",
    "\n",
    "\tout, err := os.OpenFile(dst, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0o644)\n",
    "\tif err != nil {\n",
    "\t\treturn err\n",
    "\t}\n",
    "\tdefer out.Close()\n",
    "\n",
    "\tgzw := gzip.NewWriter(out)\n",
    "\tdefer gzw.Close()\n",
    "\n",
    "\t_, err = io.Copy(gzw, in)\n",
    "\treturn err\n",
    "}\n",
    "\n",
    "func decompressGzip(src, dst string) error {\n",
    "\tin, err := os.Open(src)\n",
    "\tif err != nil {\n",
    "\t\treturn fmt.Errorf(\"open gz: %w\", err)\n",
    "\t}\n",
    "\tdefer in.Close()\n",
    "\n",
    "\tgzr, err := gzip.NewReader(in)\n",
    "\tif err != nil {\n",
    "\t\treturn fmt.Errorf(\"gzip reader: %w\", err)\n",
    "\t}\n",
    "\tdefer gzr.Close()\n",
    "\n",
    "\tout, err := os.OpenFile(dst, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0o644)\n",
    "\tif err != nil {\n",
    "\t\treturn fmt.Errorf(\"open dst: %w\", err)\n",
    "\t}\n",
    "\tdefer out.Close()\n",
    "\n",
    "\tif _, err := io.Copy(out, gzr); err != nil {\n",
    "\t\treturn fmt.Errorf(\"copy decompress: %w\", err)\n",
    "\t}\n",
    "\treturn nil\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 抽象压缩器（GZIP / ZLIB）策略"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[GZIP] 压缩完成 -> data/payload.txt.gz\n",
      "[ZLIB] 压缩完成 -> data/payload.txt.zlib\n"
     ]
    }
   ],
   "source": [
    "// 通过 io.WriteCloser 抽象压缩器，后续可以轻松扩展到 compress/bzip2、compress/lzw 等算法（视标准库支持而定）\n",
    "\n",
    "package main\n",
    "\n",
    "import (\n",
    "\t\"compress/gzip\"\n",
    "\t\"compress/zlib\"\n",
    "\t\"fmt\"\n",
    "\t\"io\"\n",
    "\t\"os\"\n",
    "\t\"path/filepath\"\n",
    ")\n",
    "\n",
    "const (\n",
    "\tGZIP = 1\n",
    "\tZLIB = 2\n",
    ")\n",
    "\n",
    "func main() {\n",
    "\tsrc := filepath.Join(\"data\", \"payload.txt\")\n",
    "\tif err := os.MkdirAll(filepath.Dir(src), 0o755); err != nil {\n",
    "\t\tpanic(err)\n",
    "\t}\n",
    "\tif err := os.WriteFile(src, []byte(\"桃花尽日随流水，洞在清溪何处边。\\n\"), 0o644); err != nil {\n",
    "\t\tpanic(err)\n",
    "\t}\n",
    "\n",
    "\tfor _, algo := range []int{GZIP, ZLIB} {\n",
    "\t\tif err := compressWithStrategy(src, algo); err != nil {\n",
    "\t\t\tpanic(err)\n",
    "\t\t}\n",
    "\t}\n",
    "}\n",
    "\n",
    "func compressWithStrategy(src string, algorithm int) error {\n",
    "\toutPath := fmt.Sprintf(\"%s.%s\", src, suffix(algorithm))\n",
    "\n",
    "\treader, err := os.Open(src)\n",
    "\tif err != nil {\n",
    "\t\treturn fmt.Errorf(\"open src: %w\", err)\n",
    "\t}\n",
    "\tdefer reader.Close()\n",
    "\n",
    "\twriter, err := os.OpenFile(outPath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0o644)\n",
    "\tif err != nil {\n",
    "\t\treturn fmt.Errorf(\"open dst: %w\", err)\n",
    "\t}\n",
    "\tdefer func() {\n",
    "\t\tif cerr := writer.Close(); cerr != nil {\n",
    "\t\t\tfmt.Println(\"close writer error:\", cerr)\n",
    "\t\t}\n",
    "\t}()\n",
    "\n",
    "\tcompressor, err := makeCompressor(writer, algorithm)\n",
    "\tif err != nil {\n",
    "\t\treturn fmt.Errorf(\"make compressor: %w\", err)\n",
    "\t}\n",
    "\tdefer func() {\n",
    "\t\tif cerr := compressor.Close(); cerr != nil {\n",
    "\t\t\tfmt.Println(\"compressor close error:\", cerr)\n",
    "\t\t}\n",
    "\t}()\n",
    "\n",
    "\tif _, err := io.Copy(compressor, reader); err != nil {\n",
    "\t\treturn fmt.Errorf(\"copy: %w\", err)\n",
    "\t}\n",
    "\n",
    "\tfmt.Printf(\"[%s] 压缩完成 -> %s\\n\", name(algorithm), outPath)\n",
    "\treturn nil\n",
    "}\n",
    "\n",
    "func makeCompressor(dst io.Writer, algorithm int) (io.WriteCloser, error) {\n",
    "\tswitch algorithm {\n",
    "\tcase GZIP:\n",
    "\t\treturn gzip.NewWriter(dst), nil\n",
    "\tcase ZLIB:\n",
    "\t\treturn zlib.NewWriter(dst), nil\n",
    "\tdefault:\n",
    "\t\treturn nil, fmt.Errorf(\"unsupported algorithm %d\", algorithm)\n",
    "\t}\n",
    "}\n",
    "\n",
    "func suffix(algorithm int) string {\n",
    "\tswitch algorithm {\n",
    "\tcase GZIP:\n",
    "\t\treturn \"gz\"\n",
    "\tcase ZLIB:\n",
    "\t\treturn \"zlib\"\n",
    "\tdefault:\n",
    "\t\treturn \"bin\"\n",
    "\t}\n",
    "}\n",
    "\n",
    "func name(algorithm int) string {\n",
    "\tswitch algorithm {\n",
    "\tcase GZIP:\n",
    "\t\treturn \"GZIP\"\n",
    "\tcase ZLIB:\n",
    "\t\treturn \"ZLIB\"\n",
    "\tdefault:\n",
    "\t\treturn \"UNKNOWN\"\n",
    "\t}\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 83 json序列化的若干问题"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "| 场景 / 问题          | 核心概念                                                     | 关键 API / 技巧                                              | 易错点 / 注意事项                                            |\n",
    "| -------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ |\n",
    "| 基本序列化与反序列化 | 使用 `encoding/json` 包的 `Marshal`/`Unmarshal` 将结构体与 JSON 互转 | `json.Marshal(v)`、`json.Unmarshal(data, &v)`                | 反序列化必须传指针，否则无法修改目标变量                     |\n",
    "| 字段命名与忽略       | 通过结构体标签定制 JSON 字段名或忽略字段                     | 反引号标签 ``json:\"gender\"``、``json:\"-\"``                   | 标签写错或漏写引号会导致编译错误；被忽略字段在 JSON 中不存在 |\n",
    "| 可导出字段判定       | 只有首字母大写的导出字段才会参与序列化                       | 结构体字段命名                                               | 内部字段（如 `height`）会被视为零值；设计结构体时需注意导出性 |\n",
    "| 自定义时间格式       | 自定义类型实现 `MarshalJSON`/`UnmarshalJSON`                 | 实现 `MarshalJSON() ([]byte, error)`、`UnmarshalJSON([]byte) error` | `UnmarshalJSON` 必须是指针接收者以修改自身；注意时间格式与时区 |\n",
    "| 打印格式与调试       | 自定义 `String()` 格式化输出，便于日志调试                   | 实现 `String() string`                                       | `String()` 只影响打印，不影响 JSON 序列化格式                |\n",
    "| 不支持的类型         | 某些类型（如 `chan`）不支持 JSON 序列化                      | ---                                                          | 遇到错误如 `json: unsupported type: chan int`；需要捕获并处理 |\n",
    "| 嵌套结构体           | 支持匿名或命名嵌套结构体                                     | 直接定义结构体嵌套                                           | 匿名结构体便于快速构造 JSON；复杂场景可拆分命名类型          |\n",
    "| 反射机理             | `encoding/json` 底层通过反射判定字段、标签、可导出性，并调用自定义方法 | ---                                                          | 理解反射机制有助于排查序列化异常                             |\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 示例"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 基本序列化 / 标签重命名 / 忽略字段"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "序列化结果: {\"name\":\"大乔乔\",\"gender\":1,\"birthday\":\"2023-09-29T20:14:11+08:00\"}\n",
      "反序列化结果: {Name:大乔乔 Age:0 Sex:1 height:0 Birthday:2023-09-29 20:14:11 +0800 CST}\n"
     ]
    }
   ],
   "source": [
    "package main\n",
    "\n",
    "import (\n",
    "\t\"encoding/json\"\n",
    "\t\"fmt\"\n",
    "\t\"time\"\n",
    ")\n",
    "\n",
    "type User struct {\n",
    "\tName     string    `json:\"name\"`\n",
    "\tAge      int       `json:\"-\"`        // 忽略序列化\n",
    "\tSex      int       `json:\"gender\"`   // 重命名\n",
    "\theight   float32   // 不可导出，序列化时为零值\n",
    "\tBirthday time.Time `json:\"birthday\"` // 默认 RFC3339 格式\n",
    "}\n",
    "\n",
    "func main() {\n",
    "\tu := User{\n",
    "\t\tName:     \"大乔乔\",\n",
    "\t\tAge:      18,\n",
    "\t\tSex:      1,\n",
    "\t\theight:   170.5,\n",
    "\t\tBirthday: time.Date(2023, 9, 29, 20, 14, 11, 0, time.Local),\n",
    "\t}\n",
    "\n",
    "\tdata, err := json.Marshal(u)\n",
    "\tif err != nil {\n",
    "\t\tpanic(fmt.Errorf(\"序列化失败: %w\", err))\n",
    "\t}\n",
    "\tfmt.Println(\"序列化结果:\", string(data))\n",
    "\n",
    "\tvar decoded User\n",
    "\tif err := json.Unmarshal(data, &decoded); err != nil {\n",
    "\t\tpanic(fmt.Errorf(\"反序列化失败: %w\", err))\n",
    "\t}\n",
    "\tfmt.Printf(\"反序列化结果: %+v\\n\", decoded)\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 自定义时间格式（参考 json.go 中的 MyDate 实现）"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "序列化结果: {\"name\":\"大乔乔\",\"created_at\":\"2023-09-29\",\"birthday\":\"1999-09-09T09:09:09+08:00\"}\n",
      "反序列化结果: {大乔乔 2023-09-29 1999-09-09 09:09:09 +0800 CST}\n"
     ]
    }
   ],
   "source": [
    "package main\n",
    "\n",
    "import (\n",
    "\t\"encoding/json\"\n",
    "\t\"fmt\"\n",
    "\t\"time\"\n",
    ")\n",
    "\n",
    "var MyDateFormat = \"2006-01-02\"\n",
    "\n",
    "// 自定义日期类型\n",
    "type MyDate time.Time\n",
    "\n",
    "func (d MyDate) MarshalJSON() ([]byte, error) {\n",
    "\ts := fmt.Sprintf(\"\\\"%s\\\"\", time.Time(d).Format(MyDateFormat))\n",
    "\treturn []byte(s), nil\n",
    "}\n",
    "\n",
    "func (d *MyDate) UnmarshalJSON(bs []byte) error {\n",
    "\tt, err := time.ParseInLocation(`\"`+MyDateFormat+`\"`, string(bs), time.Local)\n",
    "\tif err != nil {\n",
    "\t\treturn err\n",
    "\t}\n",
    "\t*d = MyDate(t)\n",
    "\treturn nil\n",
    "}\n",
    "\n",
    "func (d MyDate) String() string {\n",
    "\treturn time.Time(d).Format(MyDateFormat)\n",
    "}\n",
    "\n",
    "type Profile struct {\n",
    "\tName      string    `json:\"name\"`\n",
    "\tCreatedAt MyDate    `json:\"created_at\"`\n",
    "\tBirthday  time.Time `json:\"birthday\"`\n",
    "}\n",
    "\n",
    "func main() {\n",
    "\tprofile := Profile{\n",
    "\t\tName:      \"大乔乔\",\n",
    "\t\tCreatedAt: MyDate(time.Date(2023, 9, 29, 0, 0, 0, 0, time.Local)),\n",
    "\t\tBirthday:  time.Date(1999, 9, 9, 9, 9, 9, 0, time.Local),\n",
    "\t}\n",
    "\n",
    "\tdata, err := json.Marshal(profile)\n",
    "\tif err != nil {\n",
    "\t\tpanic(fmt.Errorf(\"序列化失败: %w\", err))\n",
    "\t}\n",
    "\tfmt.Println(\"序列化结果:\", string(data))\n",
    "\n",
    "\tvar restored Profile\n",
    "\tif err := json.Unmarshal(data, &restored); err != nil {\n",
    "\t\tpanic(fmt.Errorf(\"反序列化失败: %w\", err))\n",
    "\t}\n",
    "\tfmt.Println(\"反序列化结果:\", restored)\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 错误处理 & 不支持类型"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "序列化出错: json: unsupported type: chan int\n",
      "反序列化出错: json: cannot unmarshal number into Go struct field Invalid.channel of type chan int\n",
      "反序列化结果: {Name:张三 Ch:<nil>}\n"
     ]
    }
   ],
   "source": [
    "package main\n",
    "\n",
    "import (\n",
    "\t\"encoding/json\"\n",
    "\t\"fmt\"\n",
    ")\n",
    "\n",
    "type Invalid struct {\n",
    "\tName string   `json:\"name\"`\n",
    "\tCh   chan int `json:\"channel\"` // 不支持的类型\n",
    "}\n",
    "\n",
    "func main() {\n",
    "\tv := Invalid{\n",
    "\t\tName: \"大乔乔\",\n",
    "\t\tCh:   make(chan int),\n",
    "\t}\n",
    "\n",
    "\tif _, err := json.Marshal(v); err != nil {\n",
    "\t\tfmt.Println(\"序列化出错:\", err)\n",
    "\t}\n",
    "\n",
    "\tpayload := `{\"name\":\"张三\",\"channel\":42}`\n",
    "\tvar target Invalid\n",
    "\terr := json.Unmarshal([]byte(payload), &target)\n",
    "\tif err != nil {\n",
    "\t\tfmt.Println(\"反序列化出错:\", err)\n",
    "\t}\n",
    "\tfmt.Printf(\"反序列化结果: %+v\\n\", target)\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 84  log 与 slog "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "| 主题               | 核心能力                         | 关键 API / 机制                                              | 典型场景                     | 注意事项                                                     |\n",
    "| ------------------ | -------------------------------- | ------------------------------------------------------------ | ---------------------------- | ------------------------------------------------------------ |\n",
    "| 传统 `log` 包      | 行式日志，简洁易用               | `log.New`、`log.Printf/Println/Fatalln`                      | CLI 工具、简单服务           | 仅支持字符串输出；需手动管理输出目标、前缀、时间格式         |\n",
    "| 自定义输出目标     | 将日志写入文件或任意 `io.Writer` | `os.OpenFile` + `log.New`                                    | 业务日志落盘                 | 需使用 `os.O_CREATE`                                          |\n",
    "| 日志前缀与时间精度 | 通过 flag 控制格式               | `log.Ldate`、`log.Ltime`、`log.Lmicroseconds` 等             | 微秒级追踪                   | flag 可组合；默认 Logger 输出到 `stderr`，flag 为 `LstdFlags`（日期+时间） |\n",
    "| 现代化 `slog`      | 结构化日志，按级别过滤           | `slog.New`、`slog.Handler`、`slog.Level`                     | 微服务、云原生               | 支持 JSON/Text，多级别过滤；参数需成对出现（键+值）          |\n",
    "| Handler 扩展       | JSON、Text、自定义 Handler       | `slog.NewJSONHandler`、`slog.NewTextHandler`、自定义 `Handle` | 结构化输出、格式化时间       | 可利用 `ReplaceAttr` 调整时间格式或剔除字段                  |\n",
    "| 上下文属性         | 将请求链路信息写入日志           | `logger.InfoContext(ctx, ...)`                               | 链路追踪、携带 user/trace id | 自定义 Handler 可将 `context` 中属性合并进日志               |\n",
    "| 日志级别控制       | 过滤 DEBUG 以下日志              | `slog.LevelInfo`、`slog.SetLogLoggerLevel`                   | 生产环境降噪                 | 级别顺序：DEBUG < INFO < WARN < ERROR < FATAL                |"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 示例"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 传统 log 包写入文件并比较默认 Logger 输出"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "2025/09/30 02:18:38 3 + 4 = 7\n",
      "2025/09/30 02:18:38 Hello 大乔乔 from default logger\n"
     ]
    }
   ],
   "source": [
    "// logs/biz.log 中包含形如 \"[MY_BIZ]2025/01/07 10:30:13.643497 ...\"  的日志。\n",
    "// 终端输出来自默认 Logger，仅带 LstdFlags（精确到秒）且无前缀。 \n",
    "\n",
    "package main\n",
    "\n",
    "import (\n",
    "\t\"fmt\"\n",
    "\t\"log\"\n",
    "\t\"os\"\n",
    "\t\"path/filepath\"\n",
    ")\n",
    "\n",
    "func main() {\n",
    "\tlogPath := filepath.Join(\"logs\", \"biz.log\")\n",
    "\tlogger, cleanup, err := NewFileLogger(logPath)\n",
    "\tif err != nil {\n",
    "\t\tpanic(err)\n",
    "\t}\n",
    "\tdefer cleanup()\n",
    "\n",
    "\tlogger.Printf(\"%d + %d = %d\", 3, 4, 7)\n",
    "\tlogger.Println(\"Hello 大乔乔 from custom logger\")\n",
    "\t// logger.Fatalln(\"Fatal exit!\") // 调用会立刻 os.Exit(1)\n",
    "\n",
    "\tlog.Printf(\"%d + %d = %d\", 3, 4, 7)\n",
    "\tlog.Println(\"Hello 大乔乔 from default logger\")\n",
    "}\n",
    "\n",
    "func NewFileLogger(path string) (*log.Logger, func(), error) {\n",
    "\tif err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {\n",
    "\t\treturn nil, nil, fmt.Errorf(\"mkdir: %w\", err)\n",
    "\t}\n",
    "\tfout, err := os.OpenFile(path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o666)\n",
    "\tif err != nil {\n",
    "\t\treturn nil, nil, fmt.Errorf(\"open log file failed: %w\", err)\n",
    "\t}\n",
    "\n",
    "\tlogger := log.New(fout, \"[MY_BIZ]\", log.Ldate|log.Ltime|log.Lmicroseconds)\n",
    "\tcleanup := func() {\n",
    "\t\tif cerr := fout.Close(); cerr != nil {\n",
    "\t\t\tfmt.Println(\"close log file error:\", cerr)\n",
    "\t\t}\n",
    "\t}\n",
    "\treturn logger, cleanup, nil\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### slog 结构化日志、JSON Handler、自定义时间格式、上下文属性"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- `logs/slog.json` 保存 JSON 结构化日志，包含：\n",
    "  - 自定义时间格式 `YYYY-MM-DD HH:MM:SS.mmm`\n",
    "  - 级别、源文件、消息体\n",
    "  - 结构化键值（`\"a\":3,\"b\":4,\"sum\":7` 等）\n",
    "  - 从 `context` 注入的 `\"user\"`、`\"age\"` 字段\n",
    "- 设置 `LevelInfo` 后，`DEBUG` 级别日志被过滤。\n",
    "- 通过 `slog.SetDefault`，`log/slog` 默认 Logger 也输出 JSON。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [],
   "source": [
    "package main\n",
    "\n",
    "import (\n",
    "\t\"context\"\n",
    "\t\"fmt\"\n",
    "\t\"log/slog\"\n",
    "\t\"os\"\n",
    "\t\"path/filepath\"\n",
    ")\n",
    "\n",
    "func main() {\n",
    "\tlogPath := filepath.Join(\"logs\", \"slog.json\")\n",
    "\tlogger, cleanup, err := NewStructuredLogger(logPath)\n",
    "\tif err != nil {\n",
    "\t\tpanic(err)\n",
    "\t}\n",
    "\tdefer cleanup()\n",
    "\n",
    "\tlogger.Debug(\"加法运算\", \"a\", 3, \"b\", 4, \"sum\", 7)\n",
    "\tlogger.Info(\"加法运算\", \"a\", 3, \"b\", 4, \"sum\", 7)\n",
    "\tlogger.Error(\"Hello 大乔乔\")\n",
    "\n",
    "\tctx := AppendCtx(context.Background(), slog.String(\"user\", \"张朝阳\"))\n",
    "\tctx = AppendCtx(ctx, slog.Int(\"age\", 18))\n",
    "\tlogger.InfoContext(ctx, \"欢迎来到结构化日志示例\")\n",
    "\n",
    "\tslog.SetLogLoggerLevel(slog.LevelInfo)\n",
    "\tslog.Debug(\"这条不会输出\", \"reason\", \"level filtered\")\n",
    "\tslog.Info(\"默认 Logger 也采用结构化输出\", \"module\", \"slog\")\n",
    "}\n",
    "\n",
    "func NewStructuredLogger(path string) (*slog.Logger, func(), error) {\n",
    "\tif err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {\n",
    "\t\treturn nil, nil, fmt.Errorf(\"mkdir: %w\", err)\n",
    "\t}\n",
    "\tfout, err := os.OpenFile(path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o666)\n",
    "\tif err != nil {\n",
    "\t\treturn nil, nil, fmt.Errorf(\"open log file failed: %w\", err)\n",
    "\t}\n",
    "\n",
    "\thandler := &ContextHandler{\n",
    "\t\tBase: slog.NewJSONHandler(fout, &slog.HandlerOptions{\n",
    "\t\t\tAddSource: true,\n",
    "\t\t\tLevel:     slog.LevelInfo,\n",
    "\t\t\tReplaceAttr: func(groups []string, attr slog.Attr) slog.Attr {\n",
    "\t\t\t\tif attr.Key == slog.TimeKey {\n",
    "\t\t\t\t\tattr.Value = slog.StringValue(attr.Value.Time().Format(\"2006-01-02 15:04:05.000\"))\n",
    "\t\t\t\t}\n",
    "\t\t\t\treturn attr\n",
    "\t\t\t},\n",
    "\t\t}),\n",
    "\t}\n",
    "\n",
    "\tlogger := slog.New(handler)\n",
    "\tslog.SetDefault(logger)\n",
    "\n",
    "\tcleanup := func() {\n",
    "\t\tif cerr := fout.Close(); cerr != nil {\n",
    "\t\t\tfmt.Println(\"close slog file error:\", cerr)\n",
    "\t\t}\n",
    "\t}\n",
    "\treturn logger, cleanup, nil\n",
    "}\n",
    "\n",
    "type contextKey struct{}\n",
    "\n",
    "type ContextHandler struct {\n",
    "\tBase slog.Handler\n",
    "}\n",
    "\n",
    "func (h *ContextHandler) Enabled(ctx context.Context, level slog.Level) bool {\n",
    "\treturn h.Base.Enabled(ctx, level)\n",
    "}\n",
    "\n",
    "func (h *ContextHandler) Handle(ctx context.Context, record slog.Record) error {\n",
    "\tif attrs, ok := ctx.Value(contextKey{}).([]slog.Attr); ok && len(attrs) > 0 {\n",
    "\t\trecord.AddAttrs(attrs...)\n",
    "\t}\n",
    "\treturn h.Base.Handle(ctx, record)\n",
    "}\n",
    "\n",
    "func (h *ContextHandler) WithAttrs(attrs []slog.Attr) slog.Handler {\n",
    "\treturn &ContextHandler{Base: h.Base.WithAttrs(attrs)}\n",
    "}\n",
    "\n",
    "func (h *ContextHandler) WithGroup(name string) slog.Handler {\n",
    "\treturn &ContextHandler{Base: h.Base.WithGroup(name)}\n",
    "}\n",
    "\n",
    "func AppendCtx(ctx context.Context, attrs ...slog.Attr) context.Context {\n",
    "\tif len(attrs) == 0 {\n",
    "\t\treturn ctx\n",
    "\t}\n",
    "\texisting, _ := ctx.Value(contextKey{}).([]slog.Attr)\n",
    "\tmerged := make([]slog.Attr, 0, len(existing)+len(attrs))\n",
    "\tmerged = append(merged, existing...)\n",
    "\tmerged = append(merged, attrs...)\n",
    "\treturn context.WithValue(ctx, contextKey{}, merged)\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{\"time\":\"2025-09-30 02:19:46.635\",\"level\":\"INFO\",\"source\":{\"function\":\"main.main\",\"file\":\"/tmp/gonb_ac3573ea/main.go\",\"line\":655},\"msg\":\"加法运算\",\"a\":3,\"b\":4,\"sum\":7}\n",
      "{\"time\":\"2025-09-30 02:19:46.635\",\"level\":\"ERROR\",\"source\":{\"function\":\"main.main\",\"file\":\"/tmp/gonb_ac3573ea/main.go\",\"line\":656},\"msg\":\"Hello 大乔乔\"}\n",
      "{\"time\":\"2025-09-30 02:19:46.635\",\"level\":\"INFO\",\"source\":{\"function\":\"main.main\",\"file\":\"/tmp/gonb_ac3573ea/main.go\",\"line\":660},\"msg\":\"欢迎来到结构化日志示例\",\"user\":\"张朝阳\",\"age\":18}\n",
      "{\"time\":\"2025-09-30 02:19:46.635\",\"level\":\"INFO\",\"source\":{\"function\":\"main.main\",\"file\":\"/tmp/gonb_ac3573ea/main.go\",\"line\":664},\"msg\":\"默认 Logger 也采用结构化输出\",\"module\":\"slog\"}\n"
     ]
    }
   ],
   "source": [
    "! cd ./logs && cat slog.json"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 85 标准输入和标准输出"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "| 主题                 | 核心概念                                                   | 关键 API / 技巧                                              | 注意要点                                                     |\n",
    "| -------------------- | ---------------------------------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ |\n",
    "| 标准输入 `os.Stdin`  | Go 启动时由操作系统预先打开的文件描述符，类型为 `*os.File` | `fmt.Scanf`、`fmt.Scan`、`fmt.Fscan`、`os.Stdin.Read`        | `Scanf` 需传入指针；格式字符串建议包含换行符；输入不匹配会保留在缓冲区等待下一次读取 |\n",
    "| `fmt.Scanf` 多值读取 | 使用格式化字符串一次读取多个值（空格/换行分隔）            | `fmt.Scanf(\"%d %d\\n\", &a, &b)`                               | 多余输入会缓存给下一次扫描；没有回车将一直阻塞               |\n",
    "| 整行读取             | 将标准输入视作普通文件，直接读取字节流                     | `os.Stdin.Read(buf)`、`bufio.Reader.ReadString('\\n')`        | 读取到的字节保留空格等字符；需预分配缓冲区并处理返回的字节数 |\n",
    "| 标准输出 `os.Stdout` | 默认输出到终端，可被重定向到文件/管道                      | `fmt.Print*`、`os.Stdout.WriteString`、`fmt.Fprintf(os.Stdout, ...)` | 行缓冲常见于 C 语言，Go 运行时通常直接刷新；输出可通过重定向保存到文件 |\n",
    "| 标准错误 `os.Stderr` | 打印错误或诊断信息，通常不与标准输出混淆                   | `fmt.Fprint(os.Stderr, ...)`、`os.Stderr.WriteString`        | 常无缓冲，适合立即输出；重定向 STDOUT 时，STDERR 仍会显示在终端 |\n",
    "| 文件描述符           | `Fd()` 返回底层描述符（类 Unix 中习惯是 0/1/2）            | `os.Stdin.Fd()`、`os.Stdout.Fd()`                            | Go 运行时不保证固定值；一般无需直接操作 FD                   |\n",
    "| 阻塞与缓冲           | `Scanf`/`Read` 在无输入时阻塞；回车解除阻塞                | `fmt.Scanf`、`os.Stdin.Read`                                 | 输入格式错误或缺少换行会导致后续读取异常                     |\n",
    "| 与测试/自动化        | 可通过管道或重定向向标准输入提供数据                       | `echo \"...\"`                                                 | g`o run main.go`                                             |"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 示例"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 使用 fmt.Scanf 读取单词与多值"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- 第一行输入 `hello`，`Scanf(\"%s\\n\")` 会读取到第一个空格或换行前的内容。\n",
    "- 第二行输入 `23 45`，两个整数分别赋值给 `a`、`b`。\n",
    "- 若输入格式与占位符不匹配，`Scanf` 会返回错误并可能在输入缓冲中留下残留数据。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "package main\n",
    "\n",
    "import (\n",
    "\t\"fmt\"\n",
    ")\n",
    "\n",
    "func main() {\n",
    "\tfmt.Println(\"请输入一个单词：\")\n",
    "\tvar word string\n",
    "\tif _, err := fmt.Scanf(\"%s\\n\", &word); err != nil {\n",
    "\t\tfmt.Println(\"读取单词失败：\", err)\n",
    "\t\treturn\n",
    "\t}\n",
    "\tfmt.Println(\"收到单词：\", word)\n",
    "\n",
    "\tfmt.Println(\"请输入两个整数（以空格分隔）：\")\n",
    "\tvar a, b int\n",
    "\tif _, err := fmt.Scanf(\"%d %d\\n\", &a, &b); err != nil {\n",
    "\t\tfmt.Println(\"读取整数失败：\", err)\n",
    "\t\treturn\n",
    "\t}\n",
    "\tfmt.Printf(\"%d + %d = %d\\n\", a, b, a+b)\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 整行读取（保留空格）"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- `os.Stdin.Read` 类似文件读取，返回本次读取的字节数。\n",
    "- 输入中包含的空格、中文等都会原样保留。\n",
    "- 若输入超过缓冲区长度，会在下一次读取时继续取出剩余内容。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "package main\n",
    "\n",
    "import (\n",
    "\t\"fmt\"\n",
    "\t\"os\"\n",
    ")\n",
    "\n",
    "func main() {\n",
    "\tfmt.Println(\"请输入一行文本（包含空格也会被保留）：\")\n",
    "\tbuf := make([]byte, 256)\n",
    "\tn, err := os.Stdin.Read(buf)\n",
    "\tif err != nil {\n",
    "\t\tfmt.Println(\"读取失败：\", err)\n",
    "\t\treturn\n",
    "\t}\n",
    "\tfmt.Printf(\"读取字节数：%d\\n\", n)\n",
    "\tfmt.Printf(\"内容：%q\\n\", string(buf[:n]))\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Stdout 与 Stderr 输出对比"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "文件描述符： os.Stdin = 0 os.Stdout = 1 os.Stderr = 2\n",
      "使用 fmt.Println 输出（Stdout）\n",
      "直接写入 Stdout\n",
      "fmt.Fprintf -> Stdout: hello stdout\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "直接写入 Stderr（无需换行也会立即显示）\n",
      "fmt.Fprint -> Stderr: hello stderr\n"
     ]
    }
   ],
   "source": [
    "package main\n",
    "\n",
    "import (\n",
    "\t\"fmt\"\n",
    "\t\"os\"\n",
    ")\n",
    "\n",
    "func main() {\n",
    "\tfmt.Println(\"文件描述符：\",\n",
    "\t\t\"os.Stdin =\", os.Stdin.Fd(),\n",
    "\t\t\"os.Stdout =\", os.Stdout.Fd(),\n",
    "\t\t\"os.Stderr =\", os.Stderr.Fd(),\n",
    "\t)\n",
    "\n",
    "\tfmt.Println(\"使用 fmt.Println 输出（Stdout）\")\n",
    "\tos.Stdout.WriteString(\"直接写入 Stdout\\n\")\n",
    "\tos.Stderr.WriteString(\"直接写入 Stderr（无需换行也会立即显示）\\n\")\n",
    "\n",
    "\tfmt.Fprintf(os.Stdout, \"fmt.Fprintf -> Stdout: %s\\n\", \"hello stdout\")\n",
    "\tfmt.Fprint(os.Stderr, \"fmt.Fprint -> Stderr: hello stderr\\n\")\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- 直接运行：STDOUT 与 STDERR 均显示在终端。\n",
    "- 将标准输出重定向到文件，只保留 STDERR：\n",
    "\n",
    "```bash\n",
    "go run . >out.log\n",
    "```\n",
    "\n",
    "- `out.log` 中只包含 STDOUT 内容；\n",
    "- 终端仍显示来自 STDERR 的一行文本。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 86 用go执行系统命令"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "| 主题            | 核心能力                         | 关键 API / 技巧                                     | 场景 / 注意事项                                              |\n",
    "| --------------- | -------------------------------- | --------------------------------------------------- | ------------------------------------------------------------ |\n",
    "| 查找命令        | 校验系统中是否存在目标可执行文件 | `exec.LookPath(\"go\")`                               | 找不到命令需提前报错；受 `PATH` 环境变量影响                 |\n",
    "| 创建命令        | 构造待执行的外部命令             | `exec.Command(name, arg1, arg2, ...)`               | 每个参数必须分开传递；命令可能阻塞，需关注安全性与权限       |\n",
    "| 获取输出        | 直接获取标准输出（自动运行）     | `cmd.Output()`、`cmd.CombinedOutput()`              | 适合一次性任务；若命令失败需检查 `error`（含退出状态）与 `stderr` |\n",
    "| 细粒度控制      | 自行设置标准输出/错误            | `cmd.Stdout = io.Writer`、`cmd.Stderr = io.Writer`  | 捕获或重定向输出；可结合 `bytes.Buffer`、文件、管道          |\n",
    "| 无需输出        | 只关心执行结果                   | `cmd.Run()`                                         | 执行失败可从返回 `error` 获取退出状态、`stderr` 内容         |\n",
    "| 并发控制        | 支持异步、取消                   | `cmd.Start()` + `cmd.Wait()`、`exec.CommandContext` | 长时间任务宜设置超时；`CommandContext` 支持 `context` 取消   |\n",
    "| 脚本/二进制执行 | 调用 Python / C 编译产物等       | `exec.Command(\"python\", \"script.py\")`               | 脚本路径、参数需拆分；确保依赖环境就绪                       |\n",
    "| 性能与安全      | 外部命令开销较大                 | ——                                                  | 不推荐在高性能服务中频繁调用；避免命令注入（参数严禁拼接字符串） |"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 示例"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 查找命令路径并执行 go version"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "go 命令路径： /usr/local/go/bin/go\n",
      "go version 输出：\n",
      "go version go1.24.3 linux/amd64\n"
     ]
    }
   ],
   "source": [
    "package main\n",
    "\n",
    "import (\n",
    "\t\"fmt\"\n",
    "\t\"os/exec\"\n",
    ")\n",
    "\n",
    "func main() {\n",
    "\tpath, err := exec.LookPath(\"go\")\n",
    "\tif err != nil {\n",
    "\t\tfmt.Println(\"未找到 go 命令：\", err)\n",
    "\t\treturn\n",
    "\t}\n",
    "\tfmt.Println(\"go 命令路径：\", path)\n",
    "\n",
    "\tcmd := exec.Command(\"go\", \"version\")\n",
    "\toutput, err := cmd.Output()\n",
    "\tif err != nil {\n",
    "\t\tfmt.Println(\"执行 go version 失败：\", err)\n",
    "\t\treturn\n",
    "\t}\n",
    "\tfmt.Println(\"go version 输出：\")\n",
    "\tfmt.Print(string(output))\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 执行 Python 脚本（捕获标准输出与错误）"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "python stdout： Python 脚本执行成功！\n",
      "\n"
     ]
    }
   ],
   "source": [
    "package main\n",
    "\n",
    "import (\n",
    "\t\"bytes\"\n",
    "\t\"fmt\"\n",
    "\t\"os/exec\"\n",
    "\t\"path/filepath\"\n",
    ")\n",
    "\n",
    "func main() {\n",
    "\tscript := filepath.Join(\"86/scripts\", \"hello.py\")\n",
    "\tcmd := exec.Command(\"python3\", script)\n",
    "\n",
    "\tvar stdout bytes.Buffer\n",
    "\tvar stderr bytes.Buffer\n",
    "\tcmd.Stdout = &stdout\n",
    "\tcmd.Stderr = &stderr\n",
    "\n",
    "\tif err := cmd.Run(); err != nil {\n",
    "\t\tfmt.Println(\"python 执行失败：\", err)\n",
    "\t\tfmt.Println(\"stderr：\", stderr.String())\n",
    "\t\treturn\n",
    "\t}\n",
    "\n",
    "\tfmt.Println(\"python stdout：\", stdout.String())\n",
    "\tif stderr.Len() > 0 {\n",
    "\t\tfmt.Println(\"python stderr：\", stderr.String())\n",
    "\t}\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 删除文件并处理错误输出（模拟 rm 命令）"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "已创建文件： tmp.log\n",
      "第一次删除成功\n",
      "第二次删除失败： exit status 1: rm: cannot remove 'tmp.log': No such file or directory\n",
      "\n"
     ]
    }
   ],
   "source": [
    "package main\n",
    "\n",
    "import (\n",
    "\t\"bytes\"\n",
    "\t\"fmt\"\n",
    "\t\"os\"\n",
    "\t\"os/exec\"\n",
    ")\n",
    "\n",
    "func main() {\n",
    "\ttarget := \"tmp.log\"\n",
    "\tif err := os.WriteFile(target, []byte(\"demo\"), 0o644); err != nil {\n",
    "\t\tpanic(err)\n",
    "\t}\n",
    "\tfmt.Println(\"已创建文件：\", target)\n",
    "\n",
    "\tif err := runRm(target); err != nil {\n",
    "\t\tfmt.Println(\"第一次删除失败：\", err)\n",
    "\t} else {\n",
    "\t\tfmt.Println(\"第一次删除成功\")\n",
    "\t}\n",
    "\n",
    "\tif err := runRm(target); err != nil {\n",
    "\t\tfmt.Println(\"第二次删除失败：\", err)\n",
    "\t} else {\n",
    "\t\tfmt.Println(\"第二次删除成功\")\n",
    "\t}\n",
    "}\n",
    "\n",
    "func runRm(path string) error {\n",
    "\tcmd := exec.Command(\"rm\", path)\n",
    "\n",
    "\tvar stdout bytes.Buffer\n",
    "\tvar stderr bytes.Buffer\n",
    "\tcmd.Stdout = &stdout\n",
    "\tcmd.Stderr = &stderr\n",
    "\n",
    "\terr := cmd.Run()\n",
    "\tif err != nil {\n",
    "\t\treturn fmt.Errorf(\"%w: %s\", err, stderr.String())\n",
    "\t}\n",
    "\tif stdout.Len() > 0 {\n",
    "\t\tfmt.Println(\"rm stdout:\", stdout.String())\n",
    "\t}\n",
    "\treturn nil\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 使用 CommandContext 设置超时（防止命令挂起）"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "命令被取消： context deadline exceeded\n"
     ]
    }
   ],
   "source": [
    "// 命令会被超时上下文取消，避免一直阻塞。Windows 可用 timeout 5 或自定义脚本模拟长时间命令\n",
    "\n",
    "package main\n",
    "\n",
    "import (\n",
    "\t\"context\"\n",
    "\t\"fmt\"\n",
    "\t\"os/exec\"\n",
    "\t\"time\"\n",
    ")\n",
    "\n",
    "func main() {\n",
    "\tctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)\n",
    "\tdefer cancel()\n",
    "\n",
    "\tcmd := exec.CommandContext(ctx, \"sleep\", \"5\")\n",
    "\terr := cmd.Run()\n",
    "\tif err != nil {\n",
    "\t\tselect {\n",
    "\t\tcase <-ctx.Done():\n",
    "\t\t\tfmt.Println(\"命令被取消：\", ctx.Err())\n",
    "\t\tdefault:\n",
    "\t\t\tfmt.Println(\"命令执行失败：\", err)\n",
    "\t\t}\n",
    "\t\treturn\n",
    "\t}\n",
    "\tfmt.Println(\"命令执行成功\")\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 87 正则表达式"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "| 主题            | 核心能力                                                     | 关键 API / 技巧                                          | 使用提示                                                     |\n",
    "| --------------- | ------------------------------------------------------------ | -------------------------------------------------------- | ------------------------------------------------------------ |\n",
    "| 正则编译        | 将模式字符串解析为可复用的 `*regexp.Regexp`                  | `regexp.Compile(pattern)`、`regexp.MustCompile(pattern)` | `Compile` 返回 error，适合动态模式；`MustCompile` panic，常用于常量/全局变量 |\n",
    "| 匹配策略        | 默认左优先（Leftmost-First）；POSIX 版本左最长（Leftmost-Longest） | `regexp.Compile` vs `regexp.CompilePOSIX`                | 多数场景使用默认策略；需要 POSIX 行为时选择 POSIX 变体       |\n",
    "| 捕获组与下标    | 通过括号 `()` 获取子匹配，并返回索引范围                     | `FindAllSubmatchIndex`、`FindStringSubmatch`             | 返回值采用前闭后开区间 `[start, end)`；每个捕获组占两列      |\n",
    "| 全量 / 限制次数 | 控制返回匹配数量                                             | `n = -1` 表示全部匹配；`n > 0` 仅前 `n` 次               | 用于分页、限流等需求                                         |\n",
    "| 提取 / 替换     | 获取匹配内容或重写文本                                       | `FindAllString(Submatch)`、`ReplaceAllString(Func)`      | `ReplaceAllStringFunc` 可基于逻辑动态替换                    |\n",
    "| 预处理性能      | 复用 `*Regexp` 避免重复编译；线程安全可并发使用              | ——                                                       | 常量模式推荐预编译为全局变量                                 |\n",
    "| 语法书写        | 推荐使用反引号包裹模式避免转义                               | `regexp.MustCompile(`use time (\\d+)ms`)`                 | 双引号需要额外转义 `\\\\d` 等特殊字符                          |"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 示例"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 日志耗时提取与统计"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "匹配下标： [[7 20 16 18] [27 40 36 38] [47 60 56 58]]\n",
      "第 1 次整体匹配：\"use time 38ms\"\n",
      "第 1 次捕获组：\"38\"\n",
      "第 2 次整体匹配：\"use time 20ms\"\n",
      "第 2 次捕获组：\"20\"\n",
      "第 3 次整体匹配：\"use time 55ms\"\n",
      "第 3 次捕获组：\"55\"\n",
      "耗时总和：113ms，平均耗时：37.67ms\n",
      "只取第一个匹配： [[7 20 16 18]]\n"
     ]
    }
   ],
   "source": [
    "package main\n",
    "\n",
    "import (\n",
    "\t\"fmt\"\n",
    "\t\"regexp\"\n",
    "\t\"strconv\"\n",
    ")\n",
    "\n",
    "var (\n",
    "\treTimeCost = regexp.MustCompile(`use time (\\d+)ms`)\n",
    ")\n",
    "\n",
    "func main() {\n",
    "\tlogLine := \"recall use time 38ms, sort use time 20ms, rank use time 55ms\"\n",
    "\tindexes := reTimeCost.FindAllSubmatchIndex([]byte(logLine), -1)\n",
    "\tfmt.Println(\"匹配下标：\", indexes)\n",
    "\n",
    "\tvar total int\n",
    "\tfor i, idx := range indexes {\n",
    "\t\tstart, end := idx[0], idx[1]\n",
    "\t\tgroupStart, groupEnd := idx[2], idx[3]\n",
    "\t\tfmt.Printf(\"第 %d 次整体匹配：%q\\n\", i+1, logLine[start:end])\n",
    "\t\tfmt.Printf(\"第 %d 次捕获组：%q\\n\", i+1, logLine[groupStart:groupEnd])\n",
    "\n",
    "\t\tval, err := strconv.Atoi(logLine[groupStart:groupEnd])\n",
    "\t\tif err != nil {\n",
    "\t\t\tfmt.Println(\"解析耗时失败：\", err)\n",
    "\t\t\tcontinue\n",
    "\t\t}\n",
    "\t\ttotal += val\n",
    "\t}\n",
    "\tfmt.Printf(\"耗时总和：%dms，平均耗时：%.2fms\\n\", total, float64(total)/float64(len(indexes)))\n",
    "\n",
    "\tfirstOnly := reTimeCost.FindAllSubmatchIndex([]byte(logLine), 1)\n",
    "\tfmt.Println(\"只取第一个匹配：\", firstOnly)\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### regexp.Compile 错误处理与子匹配提取"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "请输入正则表达式（用于匹配邮箱）："
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "signal: interrupt\n"
     ]
    }
   ],
   "source": [
    "// 输入示例：^([a-zA-Z0-9._%+-]+)@([a-zA-Z0-9.-]+\\.[a-zA-Z]{2,})$\n",
    "\n",
    "package main\n",
    "\n",
    "import (\n",
    "\t\"bufio\"\n",
    "\t\"fmt\"\n",
    "\t\"os\"\n",
    "\t\"regexp\"\n",
    ")\n",
    "\n",
    "func main() {\n",
    "\treader := bufio.NewReader(os.Stdin)\n",
    "\tfmt.Print(\"请输入正则表达式（用于匹配邮箱）：\")\n",
    "\tpattern, _ := reader.ReadString('\\n')\n",
    "\tpattern = pattern[:len(pattern)-1]\n",
    "\n",
    "\tre, err := regexp.Compile(pattern)\n",
    "\tif err != nil {\n",
    "\t\tfmt.Println(\"编译正则失败：\", err)\n",
    "\t\treturn\n",
    "\t}\n",
    "\n",
    "\temails := []string{\n",
    "\t\t\"user@example.com\",\n",
    "\t\t\"boss@corp.cn\",\n",
    "\t\t\"invalid@mail\",\n",
    "\t}\n",
    "\n",
    "\tfor _, email := range emails {\n",
    "\t\tif sub := re.FindStringSubmatch(email); sub != nil {\n",
    "\t\t\tfmt.Printf(\"匹配成功：%s -> %v\\n\", email, sub)\n",
    "\t\t} else {\n",
    "\t\t\tfmt.Printf(\"未匹配：%s\\n\", email)\n",
    "\t\t}\n",
    "\t}\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 使用 FindAllStringSubmatch 提取多段信息"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "原始匹配： [[[id:12] user=alice score=88 12 alice 88] [[id:13] user=bob score=92 13 bob 92] [[id:21] user=carol score=76 21 carol 76]]\n",
      "行: \"[id:12] user=alice score=88\" id=12 user=alice score=88\n",
      "行: \"[id:13] user=bob score=92\" id=13 user=bob score=92\n",
      "行: \"[id:21] user=carol score=76\" id=21 user=carol score=76\n"
     ]
    }
   ],
   "source": [
    "package main\n",
    "\n",
    "import (\n",
    "\t\"fmt\"\n",
    "\t\"regexp\"\n",
    ")\n",
    "\n",
    "func main() {\n",
    "\ttext := `\n",
    "\t\t[id:12] user=alice score=88\n",
    "\t\t[id:13] user=bob score=92\n",
    "\t\t[id:21] user=carol score=76\n",
    "\t`\n",
    "\n",
    "\tre := regexp.MustCompile(`\\[id:(\\d+)\\]\\s+user=(\\w+)\\s+score=(\\d+)`)\n",
    "\tmatches := re.FindAllStringSubmatch(text, -1)\n",
    "\n",
    "\tfmt.Println(\"原始匹配：\", matches)\n",
    "\n",
    "\tfor _, match := range matches {\n",
    "\t\tfmt.Printf(\"行: %q id=%s user=%s score=%s\\n\",\n",
    "\t\t\tmatch[0], match[1], match[2], match[3])\n",
    "\t}\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### ReplaceAllStringFunc 处理敏感信息"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "原始文本： 用户 alice 的身份证号 410123199909090012，手机号 13812345678；用户 bob 的号码 13987654321\n",
      "脱敏结果： 用户 alice 的身份证号 4101************12，手机号 138****5678；用户 bob 的号码 139****4321\n"
     ]
    }
   ],
   "source": [
    "package main\n",
    "\n",
    "import (\n",
    "\t\"fmt\"\n",
    "\t\"regexp\"\n",
    ")\n",
    "\n",
    "func main() {\n",
    "\ttext := \"用户 alice 的身份证号 410123199909090012，手机号 13812345678；用户 bob 的号码 13987654321\"\n",
    "\n",
    "\treID := regexp.MustCompile(`\\d{18}`)\n",
    "\trePhone := regexp.MustCompile(`1\\d{10}`)\n",
    "\n",
    "\tmasked := reID.ReplaceAllStringFunc(text, func(id string) string {\n",
    "\t\treturn id[:4] + \"************\" + id[len(id)-2:]\n",
    "\t})\n",
    "\tmasked = rePhone.ReplaceAllStringFunc(masked, func(phone string) string {\n",
    "\t\treturn phone[:3] + \"****\" + phone[len(phone)-4:]\n",
    "\t})\n",
    "\n",
    "\tfmt.Println(\"原始文本：\", text)\n",
    "\tfmt.Println(\"脱敏结果：\", masked)\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### POSIX 正则匹配策略对比"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "默认匹配： abbb\n",
      "POSIX 匹配： abbb\n"
     ]
    }
   ],
   "source": [
    "// 输出对比可帮助理解不同匹配策略的差异（对某些模式差异明显）\n",
    "\n",
    "package main\n",
    "\n",
    "import (\n",
    "\t\"fmt\"\n",
    "\t\"regexp\"\n",
    ")\n",
    "\n",
    "func main() {\n",
    "\tpattern := `ab*`\n",
    "\ttarget := \"abbb\"\n",
    "\n",
    "\t// 左优先：先匹配最左边，再在此基础上取最短满足\n",
    "\tdefaultRE := regexp.MustCompile(pattern)\n",
    "\tfmt.Println(\"默认匹配：\", defaultRE.FindString(target))\n",
    "\n",
    "\t// 左最长：在最左起点上尽可能长\n",
    "\tposixRE, err := regexp.CompilePOSIX(pattern)\n",
    "\tif err != nil {\n",
    "\t\tpanic(err)\n",
    "\t}\n",
    "\tfmt.Println(\"POSIX 匹配：\", posixRE.FindString(target))\n",
    "}"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Go (gonb)",
   "language": "go",
   "name": "gonb"
  },
  "language_info": {
   "codemirror_mode": "",
   "file_extension": ".go",
   "mimetype": "text/x-go",
   "name": "go",
   "nbconvert_exporter": "",
   "pygments_lexer": "",
   "version": "go1.24.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
